From f05b09f349bec32a82ab517096cc862d8ea4c28a Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Tue, 29 Nov 2022 00:08:39 -0500 Subject: [PATCH] Updates vendored subliminal to 2.1.0 Updates rarfile to 3.1 Updates stevedore to 3.5.0 Updates appdirs to 1.4.4 Updates click to 8.1.3 Updates decorator to 5.1.1 Updates dogpile.cache to 1.1.8 Updates pbr to 5.11.0 Updates pysrt to 1.1.2 Updates pytz to 2022.6 Adds importlib-metadata version 3.1.1 Adds typing-extensions version 4.1.1 Adds zipp version 3.11.0 --- libs/common/_yaml.cp37-win32.pyd | Bin 224256 -> 0 bytes libs/common/appdirs.py | 4 +- libs/common/bin/beet.exe | Bin 0 -> 108369 bytes libs/common/bin/chardetect.exe | Bin 0 -> 108383 bytes libs/common/bin/easy_install-3.7.exe | Bin 108392 -> 108392 bytes libs/common/bin/easy_install.exe | Bin 108392 -> 108392 bytes libs/common/bin/guessit.exe | Bin 0 -> 108377 bytes libs/common/bin/mid3cp.exe | Bin 0 -> 108396 bytes libs/common/bin/mid3iconv.exe | Bin 0 -> 108399 bytes libs/common/bin/mid3v2.exe | Bin 0 -> 108396 bytes libs/common/bin/moggsplit.exe | Bin 0 -> 108399 bytes libs/common/bin/mutagen-inspect.exe | Bin 0 -> 108405 bytes libs/common/bin/mutagen-pony.exe | Bin 0 -> 108402 bytes libs/common/bin/pbr.exe | Bin 0 -> 108373 bytes libs/common/bin/srt.exe | Bin 0 -> 108375 bytes libs/common/bin/subliminal.exe | Bin 0 -> 108387 bytes libs/common/bin/unidecode.exe | Bin 0 -> 108375 bytes libs/common/click/__init__.py | 156 +- libs/common/click/_bashcomplete.py | 293 -- libs/common/click/_compat.py | 949 +++--- libs/common/click/_termui_impl.py | 530 +-- libs/common/click/_textwrap.py | 23 +- libs/common/click/_unicodefun.py | 125 - libs/common/click/_winconsole.py | 294 +- libs/common/click/core.py | 2480 ++++++++++---- libs/common/click/decorators.py | 536 ++- libs/common/click/exceptions.py | 210 +- libs/common/click/formatting.py | 197 +- libs/common/click/globals.py | 40 +- libs/common/click/parser.py | 348 +- libs/common/click/py.typed | 0 libs/common/click/shell_completion.py | 580 ++++ libs/common/click/termui.py | 575 ++-- libs/common/click/testing.py | 441 ++- libs/common/click/types.py | 1003 ++++-- libs/common/click/utils.py | 552 ++-- libs/common/configobj/__init__.py | 2453 -------------- libs/common/configobj/_version.py | 2 - libs/common/configobj/validate.py | 1474 --------- libs/common/decorator.py | 221 +- libs/common/dogpile/__init__.py | 2 +- libs/common/dogpile/cache/__init__.py | 6 +- libs/common/dogpile/cache/api.py | 446 ++- .../common/dogpile/cache/backends/__init__.py | 51 +- libs/common/dogpile/cache/backends/file.py | 96 +- .../dogpile/cache/backends/memcached.py | 389 ++- libs/common/dogpile/cache/backends/memory.py | 31 +- libs/common/dogpile/cache/backends/null.py | 8 +- libs/common/dogpile/cache/backends/redis.py | 302 +- .../dogpile/cache/plugins/mako_cache.py | 14 +- libs/common/dogpile/cache/proxy.py | 53 +- libs/common/dogpile/cache/region.py | 822 +++-- libs/common/dogpile/cache/util.py | 88 +- libs/common/dogpile/core.py | 10 +- libs/common/dogpile/lock.py | 21 +- libs/common/dogpile/util/__init__.py | 7 +- libs/common/dogpile/util/compat.py | 137 +- libs/common/dogpile/util/langhelpers.py | 87 +- libs/common/dogpile/util/nameregistry.py | 26 +- libs/common/dogpile/util/readwrite_lock.py | 26 +- libs/common/importlib_metadata/__init__.py | 631 ++++ libs/common/importlib_metadata/_compat.py | 75 + libs/common/pbr/build.py | 61 + libs/common/pbr/builddoc.py | 10 +- libs/common/pbr/cmd/main.py | 13 +- libs/common/pbr/core.py | 15 + libs/common/pbr/git.py | 27 +- libs/common/pbr/hooks/files.py | 35 +- libs/common/pbr/options.py | 4 +- libs/common/pbr/packaging.py | 110 +- libs/common/pbr/tests/base.py | 11 +- libs/common/pbr/tests/test_commands.py | 4 +- libs/common/pbr/tests/test_core.py | 9 +- libs/common/pbr/tests/test_files.py | 72 +- libs/common/pbr/tests/test_integration.py | 85 +- libs/common/pbr/tests/test_packaging.py | 347 +- libs/common/pbr/tests/test_pbr_json.py | 5 +- libs/common/pbr/tests/test_setup.py | 16 +- libs/common/pbr/tests/test_util.py | 256 +- libs/common/pbr/tests/test_wsgi.py | 6 +- .../pbr/tests/testpackage/doc/source/conf.py | 17 +- libs/common/pbr/tests/util.py | 4 +- libs/common/pbr/util.py | 164 +- libs/common/pbr/version.py | 50 +- libs/common/pysrt/commands.py | 12 +- libs/common/pysrt/srtfile.py | 2 +- libs/common/pytz/__init__.py | 66 +- libs/common/pytz/exceptions.py | 15 +- libs/common/pytz/tzfile.py | 1 - libs/common/pytz/zoneinfo/Africa/Abidjan | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Accra | Bin 828 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Addis_Ababa | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Algiers | Bin 751 -> 735 bytes libs/common/pytz/zoneinfo/Africa/Asmara | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Asmera | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Bamako | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Bangui | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Banjul | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Blantyre | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Brazzaville | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Bujumbura | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Cairo | Bin 1963 -> 1955 bytes libs/common/pytz/zoneinfo/Africa/Casablanca | Bin 969 -> 2429 bytes libs/common/pytz/zoneinfo/Africa/Ceuta | Bin 2050 -> 2052 bytes libs/common/pytz/zoneinfo/Africa/Conakry | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Dakar | Bin 156 -> 148 bytes .../common/pytz/zoneinfo/Africa/Dar_es_Salaam | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Djibouti | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Douala | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/El_Aaiun | Bin 839 -> 2295 bytes libs/common/pytz/zoneinfo/Africa/Freetown | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Gaborone | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Harare | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Johannesburg | Bin 262 -> 246 bytes libs/common/pytz/zoneinfo/Africa/Juba | Bin 669 -> 679 bytes libs/common/pytz/zoneinfo/Africa/Kampala | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Khartoum | Bin 699 -> 679 bytes libs/common/pytz/zoneinfo/Africa/Kigali | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Kinshasa | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Lagos | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Libreville | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Lome | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Luanda | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Lubumbashi | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Lusaka | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Malabo | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Maputo | Bin 157 -> 149 bytes libs/common/pytz/zoneinfo/Africa/Maseru | Bin 262 -> 246 bytes libs/common/pytz/zoneinfo/Africa/Mbabane | Bin 262 -> 246 bytes libs/common/pytz/zoneinfo/Africa/Mogadishu | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Monrovia | Bin 224 -> 208 bytes libs/common/pytz/zoneinfo/Africa/Nairobi | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Africa/Ndjamena | Bin 211 -> 199 bytes libs/common/pytz/zoneinfo/Africa/Niamey | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Nouakchott | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Ouagadougou | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Porto-Novo | Bin 157 -> 235 bytes libs/common/pytz/zoneinfo/Africa/Sao_Tome | Bin 225 -> 254 bytes libs/common/pytz/zoneinfo/Africa/Timbuktu | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Africa/Tripoli | Bin 641 -> 625 bytes libs/common/pytz/zoneinfo/Africa/Tunis | Bin 701 -> 689 bytes libs/common/pytz/zoneinfo/Africa/Windhoek | Bin 979 -> 955 bytes libs/common/pytz/zoneinfo/America/Anguilla | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Antigua | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Araguaina | Bin 896 -> 870 bytes .../zoneinfo/America/Argentina/Buenos_Aires | Bin 1100 -> 1062 bytes .../pytz/zoneinfo/America/Argentina/Catamarca | Bin 1100 -> 1062 bytes .../zoneinfo/America/Argentina/ComodRivadavia | Bin 1100 -> 1062 bytes .../pytz/zoneinfo/America/Argentina/Cordoba | Bin 1100 -> 1062 bytes .../pytz/zoneinfo/America/Argentina/Jujuy | Bin 1072 -> 1034 bytes .../pytz/zoneinfo/America/Argentina/La_Rioja | Bin 1114 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/Mendoza | Bin 1100 -> 1062 bytes .../zoneinfo/America/Argentina/Rio_Gallegos | Bin 1100 -> 1062 bytes .../pytz/zoneinfo/America/Argentina/Salta | Bin 1072 -> 1034 bytes .../pytz/zoneinfo/America/Argentina/San_Juan | Bin 1114 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/San_Luis | Bin 1130 -> 1088 bytes .../pytz/zoneinfo/America/Argentina/Tucuman | Bin 1128 -> 1090 bytes .../pytz/zoneinfo/America/Argentina/Ushuaia | Bin 1100 -> 1062 bytes libs/common/pytz/zoneinfo/America/Aruba | Bin 198 -> 246 bytes libs/common/pytz/zoneinfo/America/Asuncion | Bin 2068 -> 2030 bytes libs/common/pytz/zoneinfo/America/Atikokan | Bin 336 -> 182 bytes libs/common/pytz/zoneinfo/America/Bahia | Bin 1036 -> 1010 bytes .../pytz/zoneinfo/America/Bahia_Banderas | Bin 1574 -> 1152 bytes libs/common/pytz/zoneinfo/America/Barbados | Bin 330 -> 436 bytes libs/common/pytz/zoneinfo/America/Belem | Bin 588 -> 562 bytes libs/common/pytz/zoneinfo/America/Belize | Bin 964 -> 1614 bytes .../common/pytz/zoneinfo/America/Blanc-Sablon | Bin 298 -> 246 bytes libs/common/pytz/zoneinfo/America/Boa_Vista | Bin 644 -> 618 bytes libs/common/pytz/zoneinfo/America/Bogota | Bin 262 -> 232 bytes libs/common/pytz/zoneinfo/America/Boise | Bin 2394 -> 2410 bytes .../common/pytz/zoneinfo/America/Buenos_Aires | Bin 1100 -> 1062 bytes .../common/pytz/zoneinfo/America/Campo_Grande | Bin 2002 -> 1430 bytes libs/common/pytz/zoneinfo/America/Cancun | Bin 802 -> 834 bytes libs/common/pytz/zoneinfo/America/Caracas | Bin 280 -> 250 bytes libs/common/pytz/zoneinfo/America/Catamarca | Bin 1100 -> 1062 bytes libs/common/pytz/zoneinfo/America/Cayenne | Bin 210 -> 184 bytes libs/common/pytz/zoneinfo/America/Cayman | Bin 194 -> 182 bytes libs/common/pytz/zoneinfo/America/Chicago | Bin 3576 -> 3592 bytes libs/common/pytz/zoneinfo/America/Chihuahua | Bin 1508 -> 1102 bytes .../pytz/zoneinfo/America/Coral_Harbour | Bin 336 -> 182 bytes libs/common/pytz/zoneinfo/America/Cordoba | Bin 1100 -> 1062 bytes libs/common/pytz/zoneinfo/America/Costa_Rica | Bin 332 -> 316 bytes libs/common/pytz/zoneinfo/America/Creston | Bin 224 -> 360 bytes libs/common/pytz/zoneinfo/America/Cuiaba | Bin 1974 -> 1402 bytes libs/common/pytz/zoneinfo/America/Curacao | Bin 198 -> 246 bytes libs/common/pytz/zoneinfo/America/Dawson | Bin 2084 -> 1614 bytes libs/common/pytz/zoneinfo/America/Denver | Bin 2444 -> 2460 bytes libs/common/pytz/zoneinfo/America/Detroit | Bin 2174 -> 2230 bytes libs/common/pytz/zoneinfo/America/Dominica | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Edmonton | Bin 2388 -> 2332 bytes libs/common/pytz/zoneinfo/America/Eirunepe | Bin 676 -> 642 bytes libs/common/pytz/zoneinfo/America/El_Salvador | Bin 236 -> 224 bytes libs/common/pytz/zoneinfo/America/Ensenada | Bin 2342 -> 2374 bytes libs/common/pytz/zoneinfo/America/Fort_Wayne | Bin 1666 -> 1682 bytes libs/common/pytz/zoneinfo/America/Fortaleza | Bin 728 -> 702 bytes libs/common/pytz/zoneinfo/America/Godthab | Bin 1878 -> 1864 bytes libs/common/pytz/zoneinfo/America/Grand_Turk | Bin 1872 -> 1834 bytes libs/common/pytz/zoneinfo/America/Grenada | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Guadeloupe | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Guatemala | Bin 292 -> 280 bytes libs/common/pytz/zoneinfo/America/Guayaquil | Bin 262 -> 232 bytes libs/common/pytz/zoneinfo/America/Guyana | Bin 252 -> 248 bytes libs/common/pytz/zoneinfo/America/Havana | Bin 2428 -> 2416 bytes libs/common/pytz/zoneinfo/America/Hermosillo | Bin 440 -> 456 bytes .../zoneinfo/America/Indiana/Indianapolis | Bin 1666 -> 1682 bytes .../common/pytz/zoneinfo/America/Indiana/Knox | Bin 2428 -> 2444 bytes .../pytz/zoneinfo/America/Indiana/Marengo | Bin 1722 -> 1738 bytes .../pytz/zoneinfo/America/Indiana/Petersburg | Bin 1904 -> 1920 bytes .../pytz/zoneinfo/America/Indiana/Tell_City | Bin 1726 -> 1700 bytes .../pytz/zoneinfo/America/Indiana/Vevay | Bin 1414 -> 1430 bytes .../pytz/zoneinfo/America/Indiana/Vincennes | Bin 1694 -> 1710 bytes .../pytz/zoneinfo/America/Indiana/Winamac | Bin 1778 -> 1794 bytes .../common/pytz/zoneinfo/America/Indianapolis | Bin 1666 -> 1682 bytes libs/common/pytz/zoneinfo/America/Inuvik | Bin 1914 -> 1894 bytes libs/common/pytz/zoneinfo/America/Jamaica | Bin 498 -> 482 bytes libs/common/pytz/zoneinfo/America/Jujuy | Bin 1072 -> 1034 bytes .../pytz/zoneinfo/America/Kentucky/Louisville | Bin 2772 -> 2788 bytes .../pytz/zoneinfo/America/Kentucky/Monticello | Bin 2352 -> 2368 bytes libs/common/pytz/zoneinfo/America/Knox_IN | Bin 2428 -> 2444 bytes libs/common/pytz/zoneinfo/America/Kralendijk | Bin 198 -> 246 bytes libs/common/pytz/zoneinfo/America/La_Paz | Bin 248 -> 218 bytes libs/common/pytz/zoneinfo/America/Lima | Bin 422 -> 392 bytes libs/common/pytz/zoneinfo/America/Los_Angeles | Bin 2836 -> 2852 bytes libs/common/pytz/zoneinfo/America/Louisville | Bin 2772 -> 2788 bytes .../pytz/zoneinfo/America/Lower_Princes | Bin 198 -> 246 bytes libs/common/pytz/zoneinfo/America/Maceio | Bin 756 -> 730 bytes libs/common/pytz/zoneinfo/America/Managua | Bin 454 -> 430 bytes libs/common/pytz/zoneinfo/America/Manaus | Bin 616 -> 590 bytes libs/common/pytz/zoneinfo/America/Marigot | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Martinique | Bin 248 -> 232 bytes libs/common/pytz/zoneinfo/America/Matamoros | Bin 1402 -> 1418 bytes libs/common/pytz/zoneinfo/America/Mazatlan | Bin 1550 -> 1128 bytes libs/common/pytz/zoneinfo/America/Mendoza | Bin 1100 -> 1062 bytes libs/common/pytz/zoneinfo/America/Merida | Bin 1442 -> 1004 bytes libs/common/pytz/zoneinfo/America/Metlakatla | Bin 1409 -> 1423 bytes libs/common/pytz/zoneinfo/America/Mexico_City | Bin 1604 -> 1222 bytes libs/common/pytz/zoneinfo/America/Miquelon | Bin 1682 -> 1652 bytes libs/common/pytz/zoneinfo/America/Monterrey | Bin 1402 -> 980 bytes libs/common/pytz/zoneinfo/America/Montevideo | Bin 1550 -> 1496 bytes libs/common/pytz/zoneinfo/America/Montserrat | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Nassau | Bin 2270 -> 3494 bytes libs/common/pytz/zoneinfo/America/New_York | Bin 3536 -> 3552 bytes libs/common/pytz/zoneinfo/America/Nipigon | Bin 2122 -> 3494 bytes libs/common/pytz/zoneinfo/America/Noronha | Bin 728 -> 702 bytes .../pytz/zoneinfo/America/North_Dakota/Beulah | Bin 2380 -> 2396 bytes .../pytz/zoneinfo/America/North_Dakota/Center | Bin 2380 -> 2396 bytes .../zoneinfo/America/North_Dakota/New_Salem | Bin 2380 -> 2396 bytes libs/common/pytz/zoneinfo/America/Nuuk | Bin 0 -> 1864 bytes libs/common/pytz/zoneinfo/America/Ojinaga | Bin 1508 -> 1102 bytes libs/common/pytz/zoneinfo/America/Panama | Bin 194 -> 182 bytes libs/common/pytz/zoneinfo/America/Paramaribo | Bin 282 -> 248 bytes libs/common/pytz/zoneinfo/America/Phoenix | Bin 344 -> 360 bytes .../pytz/zoneinfo/America/Port-au-Prince | Bin 1446 -> 1434 bytes .../pytz/zoneinfo/America/Port_of_Spain | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Porto_Acre | Bin 648 -> 614 bytes libs/common/pytz/zoneinfo/America/Porto_Velho | Bin 588 -> 562 bytes .../common/pytz/zoneinfo/America/Punta_Arenas | Bin 1902 -> 1902 bytes libs/common/pytz/zoneinfo/America/Rainy_River | Bin 2122 -> 2868 bytes .../common/pytz/zoneinfo/America/Rankin_Inlet | Bin 1916 -> 1892 bytes libs/common/pytz/zoneinfo/America/Recife | Bin 728 -> 702 bytes libs/common/pytz/zoneinfo/America/Resolute | Bin 1916 -> 1892 bytes libs/common/pytz/zoneinfo/America/Rio_Branco | Bin 648 -> 614 bytes libs/common/pytz/zoneinfo/America/Rosario | Bin 1100 -> 1062 bytes .../common/pytz/zoneinfo/America/Santa_Isabel | Bin 2342 -> 2374 bytes libs/common/pytz/zoneinfo/America/Santarem | Bin 618 -> 588 bytes libs/common/pytz/zoneinfo/America/Santiago | Bin 2529 -> 2515 bytes .../pytz/zoneinfo/America/Santo_Domingo | Bin 482 -> 458 bytes libs/common/pytz/zoneinfo/America/Sao_Paulo | Bin 2002 -> 1430 bytes .../common/pytz/zoneinfo/America/Scoresbysund | Bin 1916 -> 1902 bytes libs/common/pytz/zoneinfo/America/Shiprock | Bin 2444 -> 2460 bytes .../pytz/zoneinfo/America/St_Barthelemy | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/St_Kitts | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/St_Lucia | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/St_Thomas | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/St_Vincent | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Tegucigalpa | Bin 264 -> 252 bytes libs/common/pytz/zoneinfo/America/Thule | Bin 1514 -> 1502 bytes libs/common/pytz/zoneinfo/America/Thunder_Bay | Bin 2202 -> 3494 bytes libs/common/pytz/zoneinfo/America/Tijuana | Bin 2342 -> 2374 bytes libs/common/pytz/zoneinfo/America/Tortola | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Vancouver | Bin 2892 -> 2892 bytes libs/common/pytz/zoneinfo/America/Virgin | Bin 156 -> 246 bytes libs/common/pytz/zoneinfo/America/Whitehorse | Bin 2084 -> 1614 bytes libs/common/pytz/zoneinfo/America/Winnipeg | Bin 2882 -> 2868 bytes libs/common/pytz/zoneinfo/Antarctica/Casey | Bin 297 -> 370 bytes libs/common/pytz/zoneinfo/Antarctica/Davis | Bin 297 -> 283 bytes .../pytz/zoneinfo/Antarctica/DumontDUrville | Bin 202 -> 172 bytes .../common/pytz/zoneinfo/Antarctica/Macquarie | Bin 1534 -> 2260 bytes libs/common/pytz/zoneinfo/Antarctica/Mawson | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Antarctica/McMurdo | Bin 2451 -> 2437 bytes libs/common/pytz/zoneinfo/Antarctica/Palmer | Bin 1418 -> 1404 bytes libs/common/pytz/zoneinfo/Antarctica/Rothera | Bin 172 -> 150 bytes .../pytz/zoneinfo/Antarctica/South_Pole | Bin 2451 -> 2437 bytes libs/common/pytz/zoneinfo/Antarctica/Syowa | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Antarctica/Troll | Bin 1162 -> 1148 bytes libs/common/pytz/zoneinfo/Antarctica/Vostok | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Arctic/Longyearbyen | Bin 2242 -> 2298 bytes libs/common/pytz/zoneinfo/Asia/Aden | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Almaty | Bin 1017 -> 983 bytes libs/common/pytz/zoneinfo/Asia/Amman | Bin 1863 -> 1433 bytes libs/common/pytz/zoneinfo/Asia/Anadyr | Bin 1208 -> 1174 bytes libs/common/pytz/zoneinfo/Asia/Aqtau | Bin 1003 -> 969 bytes libs/common/pytz/zoneinfo/Asia/Aqtobe | Bin 1033 -> 997 bytes libs/common/pytz/zoneinfo/Asia/Ashgabat | Bin 637 -> 605 bytes libs/common/pytz/zoneinfo/Asia/Ashkhabad | Bin 637 -> 605 bytes libs/common/pytz/zoneinfo/Asia/Atyrau | Bin 1011 -> 977 bytes libs/common/pytz/zoneinfo/Asia/Baghdad | Bin 995 -> 969 bytes libs/common/pytz/zoneinfo/Asia/Bahrain | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Asia/Baku | Bin 1255 -> 1213 bytes libs/common/pytz/zoneinfo/Asia/Bangkok | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Asia/Barnaul | Bin 1241 -> 1207 bytes libs/common/pytz/zoneinfo/Asia/Beirut | Bin 2166 -> 2154 bytes libs/common/pytz/zoneinfo/Asia/Bishkek | Bin 999 -> 969 bytes libs/common/pytz/zoneinfo/Asia/Brunei | Bin 215 -> 469 bytes libs/common/pytz/zoneinfo/Asia/Calcutta | Bin 303 -> 285 bytes libs/common/pytz/zoneinfo/Asia/Chita | Bin 1243 -> 1207 bytes libs/common/pytz/zoneinfo/Asia/Choibalsan | Bin 977 -> 935 bytes libs/common/pytz/zoneinfo/Asia/Chongqing | Bin 545 -> 561 bytes libs/common/pytz/zoneinfo/Asia/Chungking | Bin 545 -> 561 bytes libs/common/pytz/zoneinfo/Asia/Colombo | Bin 404 -> 358 bytes libs/common/pytz/zoneinfo/Asia/Dacca | Bin 361 -> 323 bytes libs/common/pytz/zoneinfo/Asia/Damascus | Bin 2306 -> 1873 bytes libs/common/pytz/zoneinfo/Asia/Dhaka | Bin 361 -> 323 bytes libs/common/pytz/zoneinfo/Asia/Dili | Bin 239 -> 213 bytes libs/common/pytz/zoneinfo/Asia/Dubai | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Dushanbe | Bin 607 -> 577 bytes libs/common/pytz/zoneinfo/Asia/Gaza | Bin 2286 -> 2422 bytes libs/common/pytz/zoneinfo/Asia/Harbin | Bin 545 -> 561 bytes libs/common/pytz/zoneinfo/Asia/Hebron | Bin 2314 -> 2450 bytes libs/common/pytz/zoneinfo/Asia/Ho_Chi_Minh | Bin 375 -> 337 bytes libs/common/pytz/zoneinfo/Asia/Hong_Kong | Bin 1175 -> 1233 bytes libs/common/pytz/zoneinfo/Asia/Hovd | Bin 907 -> 877 bytes libs/common/pytz/zoneinfo/Asia/Irkutsk | Bin 1267 -> 1229 bytes libs/common/pytz/zoneinfo/Asia/Istanbul | Bin 2157 -> 1933 bytes libs/common/pytz/zoneinfo/Asia/Jakarta | Bin 383 -> 383 bytes libs/common/pytz/zoneinfo/Asia/Jayapura | Bin 237 -> 221 bytes libs/common/pytz/zoneinfo/Asia/Jerusalem | Bin 2256 -> 2388 bytes libs/common/pytz/zoneinfo/Asia/Kabul | Bin 220 -> 194 bytes libs/common/pytz/zoneinfo/Asia/Kamchatka | Bin 1184 -> 1152 bytes libs/common/pytz/zoneinfo/Asia/Karachi | Bin 403 -> 379 bytes libs/common/pytz/zoneinfo/Asia/Kashgar | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Kathmandu | Bin 224 -> 198 bytes libs/common/pytz/zoneinfo/Asia/Katmandu | Bin 224 -> 198 bytes libs/common/pytz/zoneinfo/Asia/Khandyga | Bin 1297 -> 1257 bytes libs/common/pytz/zoneinfo/Asia/Kolkata | Bin 303 -> 285 bytes libs/common/pytz/zoneinfo/Asia/Krasnoyarsk | Bin 1229 -> 1193 bytes libs/common/pytz/zoneinfo/Asia/Kuala_Lumpur | Bin 415 -> 369 bytes libs/common/pytz/zoneinfo/Asia/Kuching | Bin 507 -> 469 bytes libs/common/pytz/zoneinfo/Asia/Kuwait | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Macao | Bin 1241 -> 1227 bytes libs/common/pytz/zoneinfo/Asia/Macau | Bin 1241 -> 1227 bytes libs/common/pytz/zoneinfo/Asia/Magadan | Bin 1244 -> 1208 bytes libs/common/pytz/zoneinfo/Asia/Makassar | Bin 274 -> 254 bytes libs/common/pytz/zoneinfo/Asia/Manila | Bin 350 -> 328 bytes libs/common/pytz/zoneinfo/Asia/Muscat | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Novokuznetsk | Bin 1183 -> 1151 bytes libs/common/pytz/zoneinfo/Asia/Novosibirsk | Bin 1241 -> 1207 bytes libs/common/pytz/zoneinfo/Asia/Omsk | Bin 1229 -> 1193 bytes libs/common/pytz/zoneinfo/Asia/Oral | Bin 1025 -> 991 bytes libs/common/pytz/zoneinfo/Asia/Phnom_Penh | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Asia/Pontianak | Bin 381 -> 353 bytes libs/common/pytz/zoneinfo/Asia/Pyongyang | Bin 253 -> 237 bytes libs/common/pytz/zoneinfo/Asia/Qatar | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Asia/Qostanay | Bin 0 -> 997 bytes libs/common/pytz/zoneinfo/Asia/Qyzylorda | Bin 1017 -> 1011 bytes libs/common/pytz/zoneinfo/Asia/Rangoon | Bin 288 -> 254 bytes libs/common/pytz/zoneinfo/Asia/Riyadh | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Saigon | Bin 375 -> 337 bytes libs/common/pytz/zoneinfo/Asia/Sakhalin | Bin 1220 -> 1188 bytes libs/common/pytz/zoneinfo/Asia/Samarkand | Bin 605 -> 563 bytes libs/common/pytz/zoneinfo/Asia/Seoul | Bin 517 -> 617 bytes libs/common/pytz/zoneinfo/Asia/Shanghai | Bin 545 -> 561 bytes libs/common/pytz/zoneinfo/Asia/Singapore | Bin 415 -> 369 bytes libs/common/pytz/zoneinfo/Asia/Srednekolymsk | Bin 1230 -> 1194 bytes libs/common/pytz/zoneinfo/Asia/Taipei | Bin 781 -> 761 bytes libs/common/pytz/zoneinfo/Asia/Tashkent | Bin 621 -> 577 bytes libs/common/pytz/zoneinfo/Asia/Tbilisi | Bin 1071 -> 1021 bytes libs/common/pytz/zoneinfo/Asia/Tehran | Bin 1704 -> 1248 bytes libs/common/pytz/zoneinfo/Asia/Tel_Aviv | Bin 2256 -> 2388 bytes libs/common/pytz/zoneinfo/Asia/Thimbu | Bin 215 -> 189 bytes libs/common/pytz/zoneinfo/Asia/Thimphu | Bin 215 -> 189 bytes libs/common/pytz/zoneinfo/Asia/Tomsk | Bin 1241 -> 1207 bytes libs/common/pytz/zoneinfo/Asia/Ujung_Pandang | Bin 274 -> 254 bytes libs/common/pytz/zoneinfo/Asia/Ulaanbaatar | Bin 907 -> 877 bytes libs/common/pytz/zoneinfo/Asia/Ulan_Bator | Bin 907 -> 877 bytes libs/common/pytz/zoneinfo/Asia/Urumqi | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Asia/Ust-Nera | Bin 1276 -> 1238 bytes libs/common/pytz/zoneinfo/Asia/Vientiane | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Asia/Vladivostok | Bin 1230 -> 1194 bytes libs/common/pytz/zoneinfo/Asia/Yakutsk | Bin 1229 -> 1193 bytes libs/common/pytz/zoneinfo/Asia/Yangon | Bin 288 -> 254 bytes libs/common/pytz/zoneinfo/Asia/Yekaterinburg | Bin 1267 -> 1229 bytes libs/common/pytz/zoneinfo/Asia/Yerevan | Bin 1199 -> 1137 bytes libs/common/pytz/zoneinfo/Atlantic/Azores | Bin 3484 -> 3498 bytes libs/common/pytz/zoneinfo/Atlantic/Bermuda | Bin 1990 -> 2396 bytes libs/common/pytz/zoneinfo/Atlantic/Cape_Verde | Bin 270 -> 256 bytes libs/common/pytz/zoneinfo/Atlantic/Jan_Mayen | Bin 2242 -> 2298 bytes libs/common/pytz/zoneinfo/Atlantic/Madeira | Bin 3475 -> 3503 bytes libs/common/pytz/zoneinfo/Atlantic/Reykjavik | Bin 1174 -> 148 bytes .../pytz/zoneinfo/Atlantic/South_Georgia | Bin 172 -> 150 bytes libs/common/pytz/zoneinfo/Atlantic/St_Helena | Bin 156 -> 148 bytes libs/common/pytz/zoneinfo/Atlantic/Stanley | Bin 1242 -> 1200 bytes libs/common/pytz/zoneinfo/Australia/ACT | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/Adelaide | Bin 2233 -> 2208 bytes libs/common/pytz/zoneinfo/Australia/Brisbane | Bin 443 -> 419 bytes .../pytz/zoneinfo/Australia/Broken_Hill | Bin 2269 -> 2229 bytes libs/common/pytz/zoneinfo/Australia/Canberra | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/Currie | Bin 2214 -> 2358 bytes libs/common/pytz/zoneinfo/Australia/Darwin | Bin 318 -> 325 bytes libs/common/pytz/zoneinfo/Australia/Eucla | Bin 494 -> 456 bytes libs/common/pytz/zoneinfo/Australia/Hobart | Bin 2326 -> 2358 bytes libs/common/pytz/zoneinfo/Australia/LHI | Bin 1880 -> 1846 bytes libs/common/pytz/zoneinfo/Australia/Lindeman | Bin 513 -> 475 bytes libs/common/pytz/zoneinfo/Australia/Lord_Howe | Bin 1880 -> 1846 bytes libs/common/pytz/zoneinfo/Australia/Melbourne | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/NSW | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/North | Bin 318 -> 325 bytes libs/common/pytz/zoneinfo/Australia/Perth | Bin 470 -> 446 bytes .../common/pytz/zoneinfo/Australia/Queensland | Bin 443 -> 419 bytes libs/common/pytz/zoneinfo/Australia/South | Bin 2233 -> 2208 bytes libs/common/pytz/zoneinfo/Australia/Sydney | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/Tasmania | Bin 2326 -> 2358 bytes libs/common/pytz/zoneinfo/Australia/Victoria | Bin 2214 -> 2190 bytes libs/common/pytz/zoneinfo/Australia/West | Bin 470 -> 446 bytes .../common/pytz/zoneinfo/Australia/Yancowinna | Bin 2269 -> 2229 bytes libs/common/pytz/zoneinfo/Brazil/Acre | Bin 648 -> 614 bytes libs/common/pytz/zoneinfo/Brazil/DeNoronha | Bin 728 -> 702 bytes libs/common/pytz/zoneinfo/Brazil/East | Bin 2002 -> 1430 bytes libs/common/pytz/zoneinfo/Brazil/West | Bin 616 -> 590 bytes libs/common/pytz/zoneinfo/CET | Bin 2102 -> 2094 bytes libs/common/pytz/zoneinfo/CST6CDT | Bin 2294 -> 2310 bytes libs/common/pytz/zoneinfo/Canada/Central | Bin 2882 -> 2868 bytes libs/common/pytz/zoneinfo/Canada/Mountain | Bin 2388 -> 2332 bytes libs/common/pytz/zoneinfo/Canada/Pacific | Bin 2892 -> 2892 bytes libs/common/pytz/zoneinfo/Canada/Yukon | Bin 2084 -> 1614 bytes libs/common/pytz/zoneinfo/Chile/Continental | Bin 2529 -> 2515 bytes libs/common/pytz/zoneinfo/Chile/EasterIsland | Bin 2233 -> 2219 bytes libs/common/pytz/zoneinfo/Cuba | Bin 2428 -> 2416 bytes libs/common/pytz/zoneinfo/EET | Bin 1876 -> 1908 bytes libs/common/pytz/zoneinfo/EST | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/EST5EDT | Bin 2294 -> 2310 bytes libs/common/pytz/zoneinfo/Egypt | Bin 1963 -> 1955 bytes libs/common/pytz/zoneinfo/Eire | Bin 3522 -> 3492 bytes libs/common/pytz/zoneinfo/Etc/GMT | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/GMT+0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/GMT+1 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+10 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT+11 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT+12 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT+2 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+3 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+4 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+5 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+6 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+7 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+8 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT+9 | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/Etc/GMT-0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/GMT-1 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-10 | Bin 122 -> 118 bytes libs/common/pytz/zoneinfo/Etc/GMT-11 | Bin 122 -> 118 bytes libs/common/pytz/zoneinfo/Etc/GMT-12 | Bin 122 -> 118 bytes libs/common/pytz/zoneinfo/Etc/GMT-13 | Bin 122 -> 118 bytes libs/common/pytz/zoneinfo/Etc/GMT-14 | Bin 122 -> 118 bytes libs/common/pytz/zoneinfo/Etc/GMT-2 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-3 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-4 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-5 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-6 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-7 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-8 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT-9 | Bin 121 -> 117 bytes libs/common/pytz/zoneinfo/Etc/GMT0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/Greenwich | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/UCT | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/UTC | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/Universal | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Etc/Zulu | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Europe/Amsterdam | Bin 2940 -> 2933 bytes libs/common/pytz/zoneinfo/Europe/Astrakhan | Bin 1183 -> 1151 bytes libs/common/pytz/zoneinfo/Europe/Belfast | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/Europe/Belgrade | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/Berlin | Bin 2326 -> 2298 bytes libs/common/pytz/zoneinfo/Europe/Bratislava | Bin 2329 -> 2301 bytes libs/common/pytz/zoneinfo/Europe/Brussels | Bin 2961 -> 2933 bytes libs/common/pytz/zoneinfo/Europe/Bucharest | Bin 2212 -> 2184 bytes libs/common/pytz/zoneinfo/Europe/Budapest | Bin 2396 -> 2368 bytes libs/common/pytz/zoneinfo/Europe/Chisinau | Bin 2436 -> 2390 bytes libs/common/pytz/zoneinfo/Europe/Copenhagen | Bin 2151 -> 2298 bytes libs/common/pytz/zoneinfo/Europe/Dublin | Bin 3522 -> 3492 bytes libs/common/pytz/zoneinfo/Europe/Gibraltar | Bin 3052 -> 3068 bytes libs/common/pytz/zoneinfo/Europe/Guernsey | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/Europe/Isle_of_Man | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/Europe/Istanbul | Bin 2157 -> 1933 bytes libs/common/pytz/zoneinfo/Europe/Jersey | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/Europe/Kaliningrad | Bin 1509 -> 1493 bytes libs/common/pytz/zoneinfo/Europe/Kiev | Bin 2088 -> 2120 bytes libs/common/pytz/zoneinfo/Europe/Kirov | Bin 1153 -> 1139 bytes libs/common/pytz/zoneinfo/Europe/Kyiv | Bin 0 -> 2120 bytes libs/common/pytz/zoneinfo/Europe/Lisbon | Bin 3469 -> 3497 bytes libs/common/pytz/zoneinfo/Europe/Ljubljana | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/London | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/Europe/Luxembourg | Bin 2960 -> 2933 bytes libs/common/pytz/zoneinfo/Europe/Madrid | Bin 2628 -> 2614 bytes libs/common/pytz/zoneinfo/Europe/Malta | Bin 2620 -> 2620 bytes libs/common/pytz/zoneinfo/Europe/Minsk | Bin 1361 -> 1307 bytes libs/common/pytz/zoneinfo/Europe/Monaco | Bin 2944 -> 2962 bytes libs/common/pytz/zoneinfo/Europe/Oslo | Bin 2242 -> 2298 bytes libs/common/pytz/zoneinfo/Europe/Paris | Bin 2962 -> 2962 bytes libs/common/pytz/zoneinfo/Europe/Podgorica | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/Prague | Bin 2329 -> 2301 bytes libs/common/pytz/zoneinfo/Europe/Riga | Bin 2226 -> 2198 bytes libs/common/pytz/zoneinfo/Europe/Rome | Bin 2683 -> 2641 bytes libs/common/pytz/zoneinfo/Europe/Samara | Bin 1215 -> 1201 bytes libs/common/pytz/zoneinfo/Europe/San_Marino | Bin 2683 -> 2641 bytes libs/common/pytz/zoneinfo/Europe/Sarajevo | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/Saratov | Bin 1183 -> 1169 bytes libs/common/pytz/zoneinfo/Europe/Simferopol | Bin 1481 -> 1469 bytes libs/common/pytz/zoneinfo/Europe/Skopje | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/Sofia | Bin 2121 -> 2077 bytes libs/common/pytz/zoneinfo/Europe/Stockholm | Bin 1909 -> 2298 bytes libs/common/pytz/zoneinfo/Europe/Tallinn | Bin 2178 -> 2148 bytes libs/common/pytz/zoneinfo/Europe/Tiraspol | Bin 2436 -> 2390 bytes libs/common/pytz/zoneinfo/Europe/Ulyanovsk | Bin 1267 -> 1253 bytes libs/common/pytz/zoneinfo/Europe/Uzhgorod | Bin 2094 -> 2120 bytes libs/common/pytz/zoneinfo/Europe/Vatican | Bin 2683 -> 2641 bytes libs/common/pytz/zoneinfo/Europe/Vienna | Bin 2228 -> 2200 bytes libs/common/pytz/zoneinfo/Europe/Vilnius | Bin 2190 -> 2162 bytes libs/common/pytz/zoneinfo/Europe/Volgograd | Bin 1183 -> 1151 bytes libs/common/pytz/zoneinfo/Europe/Warsaw | Bin 2696 -> 2654 bytes libs/common/pytz/zoneinfo/Europe/Zagreb | Bin 1948 -> 1920 bytes libs/common/pytz/zoneinfo/Europe/Zaporozhye | Bin 2106 -> 2120 bytes libs/common/pytz/zoneinfo/Factory | Bin 120 -> 116 bytes libs/common/pytz/zoneinfo/GB | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/GB-Eire | Bin 3678 -> 3664 bytes libs/common/pytz/zoneinfo/GMT | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/GMT+0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/GMT-0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/GMT0 | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Greenwich | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/HST | Bin 119 -> 115 bytes libs/common/pytz/zoneinfo/Hongkong | Bin 1175 -> 1233 bytes libs/common/pytz/zoneinfo/Iceland | Bin 1174 -> 148 bytes libs/common/pytz/zoneinfo/Indian/Antananarivo | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Indian/Chagos | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Indian/Christmas | Bin 173 -> 185 bytes libs/common/pytz/zoneinfo/Indian/Cocos | Bin 182 -> 254 bytes libs/common/pytz/zoneinfo/Indian/Comoro | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Indian/Kerguelen | Bin 173 -> 185 bytes libs/common/pytz/zoneinfo/Indian/Mahe | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Indian/Maldives | Bin 211 -> 185 bytes libs/common/pytz/zoneinfo/Indian/Mauritius | Bin 253 -> 227 bytes libs/common/pytz/zoneinfo/Indian/Mayotte | Bin 271 -> 265 bytes libs/common/pytz/zoneinfo/Indian/Reunion | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Iran | Bin 1704 -> 1248 bytes libs/common/pytz/zoneinfo/Israel | Bin 2256 -> 2388 bytes libs/common/pytz/zoneinfo/Jamaica | Bin 498 -> 482 bytes libs/common/pytz/zoneinfo/Kwajalein | Bin 250 -> 302 bytes libs/common/pytz/zoneinfo/Libya | Bin 641 -> 625 bytes libs/common/pytz/zoneinfo/MET | Bin 2102 -> 2094 bytes libs/common/pytz/zoneinfo/MST | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/MST7MDT | Bin 2294 -> 2310 bytes libs/common/pytz/zoneinfo/Mexico/BajaNorte | Bin 2342 -> 2374 bytes libs/common/pytz/zoneinfo/Mexico/BajaSur | Bin 1550 -> 1128 bytes libs/common/pytz/zoneinfo/Mexico/General | Bin 1604 -> 1222 bytes libs/common/pytz/zoneinfo/NZ | Bin 2451 -> 2437 bytes libs/common/pytz/zoneinfo/NZ-CHAT | Bin 2078 -> 2054 bytes libs/common/pytz/zoneinfo/Navajo | Bin 2444 -> 2460 bytes libs/common/pytz/zoneinfo/PRC | Bin 545 -> 561 bytes libs/common/pytz/zoneinfo/PST8PDT | Bin 2294 -> 2310 bytes libs/common/pytz/zoneinfo/Pacific/Apia | Bin 1125 -> 598 bytes libs/common/pytz/zoneinfo/Pacific/Auckland | Bin 2451 -> 2437 bytes .../common/pytz/zoneinfo/Pacific/Bougainville | Bin 286 -> 254 bytes libs/common/pytz/zoneinfo/Pacific/Chatham | Bin 2078 -> 2054 bytes libs/common/pytz/zoneinfo/Pacific/Chuuk | Bin 174 -> 172 bytes libs/common/pytz/zoneinfo/Pacific/Easter | Bin 2233 -> 2219 bytes libs/common/pytz/zoneinfo/Pacific/Efate | Bin 478 -> 524 bytes libs/common/pytz/zoneinfo/Pacific/Enderbury | Bin 250 -> 220 bytes libs/common/pytz/zoneinfo/Pacific/Fakaofo | Bin 212 -> 186 bytes libs/common/pytz/zoneinfo/Pacific/Fiji | Bin 1090 -> 564 bytes libs/common/pytz/zoneinfo/Pacific/Funafuti | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Galapagos | Bin 254 -> 224 bytes libs/common/pytz/zoneinfo/Pacific/Gambier | Bin 172 -> 150 bytes libs/common/pytz/zoneinfo/Pacific/Guadalcanal | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Guam | Bin 216 -> 494 bytes libs/common/pytz/zoneinfo/Pacific/Kanton | Bin 0 -> 220 bytes libs/common/pytz/zoneinfo/Pacific/Kiritimati | Bin 254 -> 224 bytes libs/common/pytz/zoneinfo/Pacific/Kosrae | Bin 242 -> 337 bytes libs/common/pytz/zoneinfo/Pacific/Kwajalein | Bin 250 -> 302 bytes libs/common/pytz/zoneinfo/Pacific/Majuro | Bin 212 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Marquesas | Bin 181 -> 159 bytes libs/common/pytz/zoneinfo/Pacific/Midway | Bin 187 -> 175 bytes libs/common/pytz/zoneinfo/Pacific/Nauru | Bin 268 -> 238 bytes libs/common/pytz/zoneinfo/Pacific/Niue | Bin 257 -> 189 bytes libs/common/pytz/zoneinfo/Pacific/Norfolk | Bin 314 -> 866 bytes libs/common/pytz/zoneinfo/Pacific/Noumea | Bin 314 -> 290 bytes libs/common/pytz/zoneinfo/Pacific/Pago_Pago | Bin 187 -> 175 bytes libs/common/pytz/zoneinfo/Pacific/Palau | Bin 173 -> 166 bytes libs/common/pytz/zoneinfo/Pacific/Pitcairn | Bin 214 -> 188 bytes libs/common/pytz/zoneinfo/Pacific/Pohnpei | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Ponape | Bin 174 -> 152 bytes .../common/pytz/zoneinfo/Pacific/Port_Moresby | Bin 196 -> 172 bytes libs/common/pytz/zoneinfo/Pacific/Rarotonga | Bin 593 -> 589 bytes libs/common/pytz/zoneinfo/Pacific/Saipan | Bin 216 -> 494 bytes libs/common/pytz/zoneinfo/Pacific/Samoa | Bin 187 -> 175 bytes libs/common/pytz/zoneinfo/Pacific/Tahiti | Bin 173 -> 151 bytes libs/common/pytz/zoneinfo/Pacific/Tarawa | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Tongatapu | Bin 384 -> 358 bytes libs/common/pytz/zoneinfo/Pacific/Truk | Bin 174 -> 172 bytes libs/common/pytz/zoneinfo/Pacific/Wake | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Wallis | Bin 174 -> 152 bytes libs/common/pytz/zoneinfo/Pacific/Yap | Bin 174 -> 172 bytes libs/common/pytz/zoneinfo/Poland | Bin 2696 -> 2654 bytes libs/common/pytz/zoneinfo/Portugal | Bin 3469 -> 3497 bytes libs/common/pytz/zoneinfo/ROC | Bin 781 -> 761 bytes libs/common/pytz/zoneinfo/ROK | Bin 517 -> 617 bytes libs/common/pytz/zoneinfo/Singapore | Bin 415 -> 369 bytes libs/common/pytz/zoneinfo/Turkey | Bin 2157 -> 1933 bytes libs/common/pytz/zoneinfo/UCT | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/US/Arizona | Bin 344 -> 360 bytes libs/common/pytz/zoneinfo/US/Central | Bin 3576 -> 3592 bytes libs/common/pytz/zoneinfo/US/East-Indiana | Bin 1666 -> 1682 bytes libs/common/pytz/zoneinfo/US/Eastern | Bin 3536 -> 3552 bytes libs/common/pytz/zoneinfo/US/Indiana-Starke | Bin 2428 -> 2444 bytes libs/common/pytz/zoneinfo/US/Michigan | Bin 2174 -> 2230 bytes libs/common/pytz/zoneinfo/US/Mountain | Bin 2444 -> 2460 bytes libs/common/pytz/zoneinfo/US/Pacific | Bin 2836 -> 2852 bytes libs/common/pytz/zoneinfo/US/Samoa | Bin 187 -> 175 bytes libs/common/pytz/zoneinfo/UTC | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/Universal | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/WET | Bin 1873 -> 1905 bytes libs/common/pytz/zoneinfo/Zulu | Bin 118 -> 114 bytes libs/common/pytz/zoneinfo/iso3166.tab | 8 +- libs/common/pytz/zoneinfo/leapseconds | 54 +- libs/common/pytz/zoneinfo/posixrules | Bin 3536 -> 0 bytes libs/common/pytz/zoneinfo/tzdata.zi | 2881 ++++++++-------- libs/common/pytz/zoneinfo/zone.tab | 35 +- libs/common/pytz/zoneinfo/zone1970.tab | 120 +- libs/common/rarfile.py | 109 +- libs/common/stevedore/__init__.py | 1 - libs/common/stevedore/_cache.py | 203 ++ libs/common/stevedore/dispatch.py | 2 +- libs/common/stevedore/driver.py | 10 +- libs/common/stevedore/enabled.py | 2 +- libs/common/stevedore/example/base.py | 20 +- .../stevedore/example/load_as_driver.py | 16 +- .../stevedore/example/load_as_extension.py | 16 +- libs/common/stevedore/example/setup.py | 19 +- libs/common/stevedore/example/simple.py | 16 +- libs/common/stevedore/example2/fields.py | 15 + libs/common/stevedore/example2/setup.py | 19 +- libs/common/stevedore/extension.py | 56 +- libs/common/stevedore/hook.py | 4 +- libs/common/stevedore/named.py | 4 +- libs/common/stevedore/sphinxext.py | 33 +- libs/common/stevedore/tests/test_cache.py | 56 + libs/common/stevedore/tests/test_callback.py | 3 +- libs/common/stevedore/tests/test_dispatch.py | 2 +- libs/common/stevedore/tests/test_driver.py | 13 +- libs/common/stevedore/tests/test_extension.py | 57 +- libs/common/stevedore/tests/test_named.py | 4 +- libs/common/stevedore/tests/test_sphinxext.py | 20 +- .../stevedore/tests/test_test_manager.py | 16 +- libs/common/subliminal/__init__.py | 2 +- libs/common/subliminal/cache.py | 23 +- libs/common/subliminal/cli.py | 55 +- libs/common/subliminal/core.py | 153 +- libs/common/subliminal/exceptions.py | 4 +- libs/common/subliminal/extensions.py | 14 +- libs/common/subliminal/matches.py | 229 ++ libs/common/subliminal/providers/__init__.py | 37 +- libs/common/subliminal/providers/addic7ed.py | 136 +- libs/common/subliminal/providers/argenteam.py | 135 + .../common/subliminal/providers/legendastv.py | 328 +- .../subliminal/providers/napiprojekt.py | 22 +- .../subliminal/providers/opensubtitles.py | 140 +- libs/common/subliminal/providers/podnapisi.py | 101 +- libs/common/subliminal/providers/shooter.py | 13 +- .../common/subliminal/providers/subscenter.py | 235 -- libs/common/subliminal/providers/thesubdb.py | 14 +- .../subliminal/providers/tvsubtitles.py | 76 +- libs/common/subliminal/refiners/hash.py | 44 + libs/common/subliminal/refiners/metadata.py | 6 +- libs/common/subliminal/refiners/omdb.py | 18 +- libs/common/subliminal/refiners/tvdb.py | 30 +- libs/common/subliminal/score.py | 90 +- libs/common/subliminal/subtitle.py | 97 +- libs/common/subliminal/subtitles/__init__.py | 88 - libs/common/subliminal/subtitles/subrip.py | 82 - libs/common/subliminal/utils.py | 58 +- libs/common/subliminal/video.py | 101 +- libs/common/typing_extensions.py | 2908 +++++++++++++++++ libs/common/zipp/__init__.py | 381 +++ libs/common/zipp/py310compat.py | 12 + 694 files changed, 16621 insertions(+), 11056 deletions(-) delete mode 100644 libs/common/_yaml.cp37-win32.pyd create mode 100644 libs/common/bin/beet.exe create mode 100644 libs/common/bin/chardetect.exe create mode 100644 libs/common/bin/guessit.exe create mode 100644 libs/common/bin/mid3cp.exe create mode 100644 libs/common/bin/mid3iconv.exe create mode 100644 libs/common/bin/mid3v2.exe create mode 100644 libs/common/bin/moggsplit.exe create mode 100644 libs/common/bin/mutagen-inspect.exe create mode 100644 libs/common/bin/mutagen-pony.exe create mode 100644 libs/common/bin/pbr.exe create mode 100644 libs/common/bin/srt.exe create mode 100644 libs/common/bin/subliminal.exe create mode 100644 libs/common/bin/unidecode.exe delete mode 100644 libs/common/click/_bashcomplete.py delete mode 100644 libs/common/click/_unicodefun.py create mode 100644 libs/common/click/py.typed create mode 100644 libs/common/click/shell_completion.py delete mode 100644 libs/common/configobj/__init__.py delete mode 100644 libs/common/configobj/_version.py delete mode 100644 libs/common/configobj/validate.py create mode 100644 libs/common/importlib_metadata/__init__.py create mode 100644 libs/common/importlib_metadata/_compat.py create mode 100644 libs/common/pbr/build.py create mode 100644 libs/common/pytz/zoneinfo/America/Nuuk create mode 100644 libs/common/pytz/zoneinfo/Asia/Qostanay create mode 100644 libs/common/pytz/zoneinfo/Europe/Kyiv create mode 100644 libs/common/pytz/zoneinfo/Pacific/Kanton delete mode 100644 libs/common/pytz/zoneinfo/posixrules create mode 100644 libs/common/stevedore/_cache.py create mode 100644 libs/common/stevedore/tests/test_cache.py create mode 100644 libs/common/subliminal/matches.py create mode 100644 libs/common/subliminal/providers/argenteam.py delete mode 100644 libs/common/subliminal/providers/subscenter.py create mode 100644 libs/common/subliminal/refiners/hash.py delete mode 100644 libs/common/subliminal/subtitles/__init__.py delete mode 100644 libs/common/subliminal/subtitles/subrip.py create mode 100644 libs/common/typing_extensions.py create mode 100644 libs/common/zipp/__init__.py create mode 100644 libs/common/zipp/py310compat.py diff --git a/libs/common/_yaml.cp37-win32.pyd b/libs/common/_yaml.cp37-win32.pyd deleted file mode 100644 index fdfc711213b32b5f0abbe4f67c7399ddec6860ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224256 zcmeFa4R{pQ*+0CSEXfiUW`U>?u>_4u4N_{*1`;6wc8Qcg!p8=M5Uf@s^@G9;MCD_3 zHo#lgrP&T@?_EJ`3v5eHT$aV_A&SJpKYIq`*qpHPX%y(b6^25pcQYxHDYqwoP4Pr?t7##tv~0bGhC~bGaT7shgW1so>s+p#{jpMdk5 zsRfsSd?tu&u10#E3mlBY*-Mm`_?5U^O%raq@#Y&=yIk3nT!H6Q)Wm-IN+zsYz3O^A zVw?asobU31@_qG|+mVrQ@mv1^!|NkE_u*mGvyL=&2^O<{Mfy0E8Sb&in zt#B)2Tn*lm$X=~Ce^*7wT8W4HZf|{)HfWjoztt~Q)qki~hT>=4+nwe&d-UC&`nXzZ zYTgxvD_kLK@nE;>U&@BSVSTsH*x+dd>AP-J-U#N>-}stX#vrOINOXbIBsy`aH3dz4 z-p4<{x4^9Sg{sW;-t@POYH!g|MQ=ym24yFaXJl{P5M@VmH1x6Fwz2cNhChYhX$v21 z(lVbTLh2mV(45y@+=g2sfzBZf%>}RL^%Qq2GvA^)jMXnPe0=zL*gW;Y!0+v}|{x(|aZg7`I znm~sySB5O><9M9uP>Cq?$^wu1a`YNd>r9Y^%=b#=e5)yu@9!wG{zEOxyn=|e;9%#l zkhqM$f%?r>KxR}QGq3j4@6|j7A9i{x#e;4`>mks7#P2JS$8auMZ)xX616D;e(1oMD z!uk;yrn$ZggkImIZZyUKc>N`>vDTx=`-`fxmHG?O&t{Cro?5=>(SrynzPVBzWTlDV6+p2LSr|P^{Doz`h#l50)c|S zvBV=Y(37}fBBdH_+MzS(2UO#&GOwcg&ERN}|Br;_;P_ZDmyi()=7|$au;d&IW5J2y z5i^8Xki?8DIma!r;9`+usW>g9Q&DiG`Z;dj+*>N6rG-ZD7VB={#0=hon(*pO3Jt?jQd!yj*?@+b>gJbsc6#{{HxO!P`%2S2&K7nPV6!H zWO~iTpeQw?GEN8(NM%%TsiFXF-x8B=lPe{fiER=^(iPQ2zZmDFt3>WvF9rc|+`px} z!JmCK%aXdp?hhwZGcuW1(de#qm0$z{9KS)`q(pvieJ;ap1jkdB z&75VD5iA@;Pgj%}!HM$uLO}F2Be+y=DyaSl3Yuy=B~Px)4}QC5=F_i{WUGYi9L=_h(mCf-(puNf6H zMMiqR9PGqrl;YoSl=@N&=v)4r)W@QJpT0K^>bSZEg&SpM$yxCJ0%8behTinC-jkth z{waE`DxOzA>0B51gU38S!Kg@Zm>axB8+__GWen&G{hKL}#E)iq5;tJ2$fvz!2BDCs z*H*-T-#H(};d*XG{46T7b2=5zd`P?|m=I+LQxrG8&a*!)Q;kq(49I5;u4NF$8**I! z3jqDAIR6<(8m{S{&H`VkBFhMPtK%0DAbs!wB*my{E?_-|Qt84Ub7kV9`lZJ32WkE! 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�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 Mg6scY2ZX@?1%)i-2LJ#7 diff --git a/libs/common/appdirs.py b/libs/common/appdirs.py index ae67001a..2acd1deb 100644 --- a/libs/common/appdirs.py +++ b/libs/common/appdirs.py @@ -13,8 +13,8 @@ See for details and usage. # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -__version_info__ = (1, 4, 3) -__version__ = '.'.join(map(str, __version_info__)) +__version__ = "1.4.4" +__version_info__ = tuple(int(segment) for segment in __version__.split(".")) import sys diff --git a/libs/common/bin/beet.exe b/libs/common/bin/beet.exe new file mode 100644 index 0000000000000000000000000000000000000000..1bc460b0a8c83b0465082eeffc557174c09871cf GIT binary patch literal 108369 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 literal 0 HcmV?d00001 diff --git a/libs/common/bin/chardetect.exe b/libs/common/bin/chardetect.exe new file mode 100644 index 0000000000000000000000000000000000000000..78c517dd81b867fe2ff31f5389d5d65173cbccd0 GIT binary patch literal 108383 zcmeFadw5jU)%ZWjWXKQ_P7p@IO-Bic#!G0tBo5RJ%;*`JC{}2xf}+8Qib}(bU_}i* zNt@v~ed)#4zP;$%+PC)dzP-K@u*HN(5-vi(8(ykWyqs}B0W}HN^ZTrQW|Da6`@GNh z?;nrOIeVXdS$plZ*IsMwwRUQ*Tjz4ST&_I+w{4fJg{Suk zDk#k~{i~yk?|JX1Bd28lkG=4tDesa#KJ3?1I@I&=Dc@7ibyGgz`N6)QPkD>ydq35t zw5a^YGUb1mdHz5>zj9mcQfc#FjbLurNVL)nYxs88p%GSZYD=wU2mVCNzLw{@99Q)S$;kf8bu9yca(9kvVm9ml^vrR!I-q`G>GNZ^tcvmFj1Tw`fDZD% z5W|pvewS(+{hSy`MGklppb3cC_!< z@h|$MW%{fb(kD6pOP~L^oj#w3zJ~Vs2kG-#R!FALiJ3n2#KKaqo`{tee@!>``%TYZ zAvWDSs+)%@UX7YtqsdvvwN2d-bF206snTti-qaeKWO__hZf7u%6VXC1N9?vp8HGbt z$J5=q87r;S&34^f$e4|1{5Q7m80e=&PpmHW&kxQE&JTVy_%+?!PrubsGZjsG&H_mA zQ+};HYAVAOZ$}fiR9ee5mn&%QXlmtKAw{$wwpraLZCf`f17340_E;ehEotl68O}?z z_Fyo%={Uuj?4YI}4_CCBFIkf)7FE?&m*#BB1OGwurHJ`#$n3Cu6PQBtS>5cm-c_yd zm7$&vBt6p082K;-_NUj{k+KuI`&jBbOy5(mhdgt;_4`wte(4luajXgG4i5JF>$9DH zLuPx#d`UNVTE7`D<#$S>tLTmKF}kZpFmlFe?$sV{v-Y20jP$OX&jnkAUs(V7XVtyb zD?14U)*?`&hGB*eDs)t|y2JbRvVO)oJ=15@?4VCZW>wIq(@~Mrk@WIydI@Ul!>+o3 z=M=Kzo*MI=be*)8{ISB{9>(!J__N-a=8R&n#W%-gTYRcuDCpB^^s3~-GP@@5&-(G& zdQS_V>w;D8SV2wM8)U9HoOaik`_z>Ep^Rpe3rnjb<}(rV`tpdmg4g@>h`BF#WAKLH zqTs?sEDwi<=6_WPwY&oS9!h@ge4(br)-Q{|OY*#YAspuHyx;~|kASS3FIH@oGSl?L zvQoe8yKukD)zqprHiFKlW%;G=hwx4l;FI%8m&(#zU|j&_bW@ThNpr9D0V}xa)%aIb zI$i2CA2mPU{0nJmK0dxe)dY-`z>ln($ z;r!UXuLDDi42|Zd3Erx&m8GqlFWbIX0V<*Gn6lVNq%gD>gw}da}r}ZQB~ns?p8uy4i0%1Ti$Vt|~OUth4=+yEmPu8{3(w zUDkd@?w?`_J9HBkx&ZF8v{+9phcT@3J8VI~wN7Ez)oJS6^dhb2N;;{RTXB`K*E$64 z3rDqRtY&&*}9yq2oUcvD7K)=@bWqC1X%l0jk)W<5-WBYC(#rn4H5)gp#eHMmwlLJq=^%|*gMQ*pq4VV(QhHA4CGj<;!d8i*#Z8CaN#*>VcCnj~;kkeUa{LUoKxFCaoQ) z(Lz++&x3Lwz;=6UnhwM!MvN17>{Qmb?dwgsTmzkLB~jD#wiGz73hc0bFE|C9KA#|= zH}%FQ>c&Y5z*TJD-<$$Y*WZx>5NNe-E-TfAt1!)%Wc@I;ZuNwxDGGasDIMyUNiVvG zq;Q70PYHcLO=Xgv2698@cJrkun-^>P2}|fMHlm7xaZmE<{&cQtb`{N9zj0bRmpW^T zzQV7oTs0ENHe&mxQ6DI7qd0SU4;3o*2qRd`X1>(=ew})X5Dx zx$lyzZM^emtdsbk^u+xwdSX$lp7h*2CkHCqDohShL)V4hM9k+UQLP(GN-H7!C8gyq zex`xuPQ(!g4}S>0r+CyH+xIAMP9Z&+?BT1!*kA<}dqRn*FwJPGe}l-sw(lGYN1b8} zWQQjQN`9tdtF?#aqMN?wu4E3)qGxzOhwr*vb;kX_%&U*-=KLr0raiGc^x8|=Wqt`N z?L0luR(~BF;DS@~yKDN7|*TJkj*-B%s1{65$`jY_(C#P&^rVi0?Ro4iaFbR)Z2NLxS0 zTL;%Kt22(A8JiL`U$i!iR&zLxx^E%H=*c-=+h@sisygu-_#m4J4LQqB?~vXvP4@yQo0-^oki(PiH+=FZl}&W)S-qI zk>W;2Zl-vl6rbe4X6feZb)l-Mv2oh^5t8q5@(Y-SPoUZ;N<5Tdl!h|=x!1}5)E;}=RcAXJ8(<$^13IV==^rU>wwq$hX3V4iuA0>h< zuxK^)myr=p7a)oeZ+g4u^9(OmpFl8J@{{UJfy=DjAf8lTTD00iSF3Kb9|GdM-PQp)0<* zZkW*V-TPpIXEKDks>&FQ?qoV&Tfa*;TJyB^yJa8xcch+*-cYj6E7HdBX!5)TIXSNM z4C2L57KVd0rioelfI{ELMrb&Y}?h%mk5iSTXrmJ zwlk6qsS{}3<}Uc!G}Wr;Tek1Tym8$SrWokvCzU(FVIAWTEa1pwE zBJ6JdS@$4RFBV*~g^Eo9MAFafx2rt|uRsR%xpNVyj8!g>2u0v=>eO zS~4nHBgR%cVxB-_OwP@%JN(CpY3qHvqsbt-TUGivY2Dr$b+=`6PJSkbWF)!Jn=iZJ zMt}mOG~-m{)L*SV+yRH!c@XR%)K^BqVRh zq&wib)2#d0V3BD*|F5o2J6$vbdJGh`O-30SrMI;e*Y&m8c0Bi^cD-$Daq1haK*i4o zS^0dLE!U;Du-W5i&*6##L30bjy7q7@lQPyCc8<%{>0)|vQlrFG_D_+v^1uh+p+bhA?!)dFEqi$(hoT?=hJt20DQXmOiJ``9LY)@=HE zO1esvSjV70vmITir9t{Om5D&<%?UTa#`5Sp-x@^?6JCK@(Y_-+ye_agHcB_zSUEYe zay}#@o~N5_?G>%q2t<~g3s!Y+G*Mj=P3Zn>mA2=HCm`lzap|)*f|(31R{)36WvAyz zfea$wK&B|2YxO{n>twI{fk3f0YVK4T;XDy#cUe=*$V6#=30zz**pkdJOUUdHcyGKx z={=%tU83}-sM&@LFz=EaBy8m5*VS4ZYhB<>lI{BnIk4cD&H_E|%!spiL(( z$1W0V$;KX^P(?<}XYHqoplpQo7H>!m)d{bdPaLde+h7(tf+ZB(6MxWZnoX6&>|)(q z*DB~wjMmL&u~F-ZIbJ>BJ5ZM6ik)gUbdlBM`Quqove#M~lf*ebB4nBg}NN8q8e!? zVj>HOMJZ@LQzOdvHUSih8gCt%IxvyHLmO^Ea(*!Nd-Zuw>`f87{SkAwbrcIp6hiff zt7^x@FVoBVwDl9eTxT2$))(-5-O9W=qunp;*yvYT{VJ=~FI-x;pN&=5ArA%W0()Z} z=?f87g#Y@j2_ct@T|gzY^?R)mq?NdksZ}7gJW^{18>hCuy{s)%iDWGzC?-DRKLl?l zlnO5zQf3*!v6nJ;)xm`Sjm!6zf=o%-07p#e5?cL}gBtB`Nq!dTtt@<7#(o8m8xm*XOvN65AL(=C_D} zJM9UyYteSSwriu8{DkKl6tSk&09e8kMrjh@N|SS;@9l|6^W@_Q=i{`@$NUzI6|VF> zN{Rev95oVSa&%)ew#+uKZf{3cFg?f64ASokLt$^COgO2#BW71L>H7~o2Zg;=Z|nCM zZ=N18^ET^uY+VpF$K*teqc&2xaTF!LhIKrwGne_WBX+B_9vi@rt2GKHy|kQxSUJ18@{fEswY{>va~$3%JGyYfr29k%@bck16c zdf9Hh?|r@PC`@3R-j=#7868z@m3)O|u0`Iw|bd&(6~U$UMGD@Vncn>Lm}{NqU9US&{gYu`~lU+m1n zi1g$#vC1#v|9B;ObTzhRor!#90$^5b(Gy`buihHrRfjV>-l^6#?Dg3lZ}@PRD|I(> zVcp1Kiyr8xABHMWk$xp&hFzvUhIKbDi1339ve8Ac5ON73NDM}^^I8O?+8zk+GVA0S zG|7G=o9JQQO;-x!z=zz5c@^<{-AWi)tG`b65v40t#CwnzKA}>?+z|q4`eNlNfRXZK%L4$WHQ)8Sgo0 zwE~@9)+4fUIf8fW?9TihJ6Hgttrta)MqB{FTBqxu|CDLzEKWn{Cn*>&wx$DtvzSvC z(4Jr-g8~qe!NL-;BVhBlx}Y;!It5;VT~^q_HdZcH!a^(MA3%zpy!zmpD(NfkvF=9= z6p^lmDSFnrRVn4npverH%%I5(CT}SgTNGB)0sCY%@`7%@lG#4Gt*2;3c3;0E8(QyS zoo-l-h2)DEIh-3t!@^Gefe~>Aq|Sbf{goW=Op7FDAB-5amdpAhatG_BQh1V>p|DF2 zoM~XblmiX(kl0U_veatKBQ+uz9@Z1{N|y`0j<11Sd^JtI@w2S`$mW?%;MWLc4%=HL zi!p2d7Nf9k{=Kw;xt19k$vh+UMEX9C2D?jRP0wn3ihvj zIKqjR_QyB+t|%#l=^@PkY$HlM{<4z$Jve9n{#ZUhYv#%_q#uJnen z7S7e0{d|oCJ_u>EJ_(yUqk*m3cisoGsENRi9?F=l*A~&-*(<$4vm*-sUaFT_dJdnX zrOQM7ERMPl>SbN2|4`NV9yZ$|0jqv#7_|5qM&SK>FdA$Qn}>sahte?IEg|!hNZ-Lw z+2M47yawJ6YgZhmd7`)o7cpN%77HvCf^&@h2FBhy;L2rI>K+Cp6&?pq zlFhyiSR(126>L@rL1c*79q1?uBeI5<%2ZP3K!*8bJ8n5Vkdy&9Re{a#rI- z6fv$Y@#|&(1pg>!eIKW$IeEqD_akO!YCNey`?q5Uh$a^MgG!T#n1>V}I*O@Oh-I-5 z%k{Du%Iw6?)MXzjh?<)@`1%M|Z2fN100q^u)YBKp;(8NX!a7BpNWL}bB60|{!@3IM z&!_-j!}^5^fVs3)8n2d}7M6&L95t6HGcO7O>k8tJiY2gy{mtC0V*s z;mM4hWAvYlP0?$+)i!p-gT`AH%yAiSovz=pXFBCU*-y1#y_wmwf!PgMrEDEyp_Y+h-3$ZW$Ny$8H)g+M&odOm3D+qCuDCyTVF4s8_v zmEyLRLz)cEXCoqszT`H8*!|T3k)9}efv(zxR?xmMPtJ#z>B&Eo77PE!jE`0XJbxM^ zJEbz?Lu5g--#l!-Y#gzXP3G6p>XOps?99>9SjC=T%MY0{>#J9bVPGK(CmAlr@LDVu zdtE8Cwy$lsu#8`O8L={lK%5}c`pb6GjOmh$5gX((WMNF8jU#kU?6HQLb+0+w?hE$3nE@wxIvFA6~zB7QMVyoEeHQuBH-S!>tRw89F zyIi51ALX;4mfyl>Gbw7NUa`Y^`9s-NepV{j;n;E-$Ceyj?qimR?nQpJ7Zt@YCfL5$ zX%(74|FeDDa8Ol;N-078H81eqW|LX(_9$cc`%a*!#=7{V2=)|lNG5a40)v6g4t z01XUUv68UZ2|@vkl?ceW7{YVw!nCy? z+sAnJ?mvd`Ab`J#GpRgV_N#doE}<~&Z?VHb%c3L;ua)NW2qzfhmeh>}dH zGKiE|U&0iVSyyQ$NO;+GkhAqI3{1v-UXl6k&ogShm<+H}bDWf8ZLbv`!7=F`^V*WW z%|fH`g0dA}vmj?dt{;}&QQW)P9h)H{A4EQ&PP7V>>J53l4KOcs^mIW( zWkEdG-lC&N1l;w9;87FIEh#42)wpNXA?u;BStwK2f%x9dIa=c%`6v*^^D7Rdeo3P2 zK9dB;uN>7oyTltCA%$60W`E3W-dBpg zuqcq@x{}^i&v~(2yR)n>8M=s-@@eAy%xR>v4&Y%h*z7^|kj=+ut-*SgnXpUQ2Za%i zw_32)!m77h`9S6v$7W)#c5Gu%xh%>rSYMFAD@|Kh-5MzR0ebF=8}-^F_#pg>cMe^Q z_fFTrqJD?X&Jg+pQE^7T9S;~YZ`N{LIq@lM=%?CSV`D_iRT3c{J=yaikxU5%rHT=TI9ln9_p;9*QY6sX)@dJei;QU6QC|w1dx9PPU z-k*1jcMjN$eZXl0=c@we30H5Z#G4Zf18#{O`?4|fubhbI#LpT6?u0J@S5*J&gl|g| zx>4w6bp!F}L5Qb)5yTF=Q~b_2auNe$u2af-1--x-Y8ugJ)$~A7xqyDQUb~z9yjp?2 zS$2CCh3xpcnb+1EDhBdlycVY?TH-GQhOBi1Em;xS%mih!zz5d%5ZTK)kgI(;YVM1) z9Y?6R=*3Ee3NQqA=9m}0tBfPY>WV^F{KDkb!>u=FvBx{<@$4HF#Ty?(D_|c16@7ar z?3sMj4pkIxD3B@pYY^(UW7-_E@LkG|E4F$T>^}02mQUF3kyHzn_+N+p{xB`ffEMeA9vW5-D%{ zZltI*4Xan_uaQoJoSn85x~zjwdZGe`c|L&8DFe`!Uzz7`w0>!xulJ>+=37i-p5mR> zWl?vJ+1b|P3AuYhVyI7#LAPEYZ87i$tRpmE}@el^F1lN0erixJ1-N#3v0fp0!puf z11^VLsS9qh<=8A zl(KovC21r`^>K0LV;-uDR<&qv-K@mIx|7<^+mo|TDsK^_F=k^064`x9BFi|CeU^vI zA`v->wGlB>5s}S`2Vld*+LS4GWdW#Z9=Ld+EhF-ng5iU)X7A68`i# zO|AEyO~DJK*d*(2vK_TGJ;J(KCFF$1nt-h(v%kz8V%#2jMxD`gWt|!-@k5${77Q@!{4z;ze=7&BScC z{l96Ke7GeU{#P5P(1-)>pb!x>_limI(??L33;=E&UU`S^Xg(o6V~Xzp2+b869oyFB~+oK91m(zDG}-Ce|yro;clXhx0fm zqA!a1;w8|CgOIS{tHtHPM)Qnv&@IQrVjZ>Cz6}8;hEX6s#`+#jXAT>_&8rE)U3h@u(3Rj2wHPF8HLr_+u|u2h!@v|soMqnSEk8Zd`9UErc zRN_h>v@U-yBXM8Ej^Rk$+sR6^P!=M|4(TT&#@8NU-8`?Hjo1~wjxi#DFXslCbHj#H zR5!NB>1Vtka3nsdw|a3-Y^?Qbif>?ajCQZ}h|~?V$4;Z2hvePt!VjWV5kP_Mdzd#2 z(Ya9OE~}OG95vq%MZN6^iVy-|(zl&p4c#oK!g~#g9ul0wCtz5||XBmlcb|@y+~5^oMA2 z%2&t|Z30b#v!su;P0>oP@n%l!68gTFk*t&4-cTiC(g?CTh0XM*M_NA`XrI~P!(S-N zL`<-L&IbV?K2X3qpYwnLW)JqoQsvmwRaiiIOAWlUuFCW7CR}XuDqc-j>a`x<)1Wa~ zw1+(1-L|GuLWkn}HjH3W>Zkjq4e-!WA;hn0iSIXW`S*t~{JgUpYShtg%LoE=slzv~<=K*WA*ElMAxu<+e5ER>PXppG$|uZeA(Temu%&q(p;3AFN2!kq zm=?vfxfpqDEN!LF)Xm0H1wg{HMEXo-l13}ryyuWqH$7J>Xgp69ORBMSo%EOR{GE@T zp6`=69Ftb3=ONylwdwgfFVgK&D$mcnFSmVb{~?FB$0_H`z~O7eOlSLUCm#&_o;kIB z^GO&pU!)Lg-zm3^a<;FL4;!T`wb1X9I%}R0*ioufT+j91NaBu?NMeOwVtj_4-Bj0@ z_j+s0>1Gh!;oi!cvc4Mg&8Yc4=Cmj3w59_z5~=-$9!bpUA~dL*qwByWnz05DbT{~4 z*jZ@K?vDlzYTtT-qUP-5@^1W$cjLZ1m)7`wc?;yk#>sw)Ni$-;5OH_f-AMb*3BElL zTXVmwcEz1Nab&8Q-#V9uW2Z6VdwH||2KhpVBR4w8!{_^EvduYpj=@m1wadC|nCyj2 zt$A%;w3fp&nPJJ87ID86l?_lyq<-5M`#ZFGH^n*bFxrb{B4*!>glHD=IX zaR4E?rmXV`e=Jb3r)umy9O_=}HG_<;wLag>;c-u)&Cx(xabWC&VP!^jmFM&Ib z$EM)|j1Ueju0pu}b54-q=pis$~y&T*+xHtN5ij^Dv z^%7mNlKsbrMJuxz??mDQn__!^I>*gYDhiq>gCh>6y-yP!!np!os_nT!v)geY)f(H$ zMdxVz82saUVjQ{l!Fyx32g`P8jl0P*QX^tlU_Sb?kt&IuWuyvXIfW6 zvj(<2h5p+D2H`EwSwH=TECv*ISR}=U4K0jI?@X;}rSnDnja37_hg1U|)xdV^hSx;N zR_l)tW>JcPb8F@5C~uO{c@SQX_Wc-vx12+X_zdyQjX9DVg;djzhq7W0o z))<;YTY1Kqwi$lJ9G%8d#&=Y2g-5J9EDiLvQu;DVkGayNG;o{qwO{JmzR6Uh$UG@x zPCO=Jtf)bg*6_lp#3+w^Tg=a7c|p*fGtm(jE${gPmO7HD77SR?ytQ3_Bxr`(@-qAT zWfSOxaSdnVed(w}=&i-FC`!Pi=?<=yrTgx#ws#DU@R`1IyXR+k0R7~IY6mXQnIYJ=|Dqf4+{O?83Q*D35 zm~q?{FH`;v)-R{BFDCMi3*t-k>{7fQ)8nw?9TyWqG3`Ursw{KR7s%pMMe3iM)dT*M`1?|}%AZgc@ zX30+IPfbP!7X!AEjBUyvWF0|-nESBQh0Mtj(=rdU9mNVG#;RgmWP&-P(zBuAracc- zp+(j}^q7=iuyEi?+-C&NiI3TU^)U0@n#|Xx-UoNc*6NmU3HqR;Wl%dL zkIaY`kZ}eU*h+@_w{SA-$LNPRs?I`9&yRXRk~$gghBqUHqL4xmtMtVD2F!n`DBU&Y zA@L!Y3w6XoW)F{rN=O!R5%FX>|1Ypcy+BCeYqX6PttY}QV(d8A+D=AhCvAj2I9Ci+ zE_xz1LN~*Y8IN@_s1s-}DbcJjI5vpO#CDDjrv=T!AxN@1Y#t5bfti^9CyoyfXpL_T z2V8Sei{e7KzA*ct9Fu(Nld9;CL z?d=gOO0=h4Y+4Jb!Gh3(cScOi?2L8L!@ zXRz-XiI$JM!z1>gk%aITI}Ha2`#~+lD$VpAZrrCeDp|VeRi;hXLX+MU&wulyCi{V@ zp~_QZXJ}92zB_-Nbp#$k+W_m_M`OPZC+5?&W-o>zKXw6;Mw zPZVMo6>O;(y{(rJ))j>Jj--v{g0^&C9d>R#xu`p+I!;{+20Fvd@~tlHPH#Z}#D#80 zwJKsBYO=M&SD3rt(@+KWTkw{8Sk2`v+CyWht11NA9@xI&HVQx{ji8>XzDsLtBV)te zncQFSH2RmvZZP^+XpO58RW`&kpI(%5tDHnrJ71E)Kc>S>es<7(F(N@%94gfc zt}u%Qr8lQ*gBzd@RpP2l;SukoBN6k<1H@t7b$bS(TH|}1=7p2j`DH3Rgr=l(6PIL> zoLb8o5hMoHL6p-P+JoNWY5<8%Jy_)&dQZbMH@;n1k5gZVSDG59CRwN@mS3YieR+R+ zBAkSWPvs4(spUN{Y+l|!Sg;6&bFUYtQyI6H=HmrUtM0Jb+GO9GuVy+uB51tb7Yv*T zYFD3tL}TJ3oc#GNW=rR=aO>o4-~yYIy{l>KgSZEC^?)4Dv_{}AeTN7(PtHQSsCppR z-O&ueZ%;ojbgn0xqy?c1=D}`fMTVQ+(Hf7#GMidk%E4&NTj|ys)55Ur?JSdKcj|Q# z@lkkIq~gI09sUQhXE1Oi`1G%+0*FVX$zZ^K;H)*Biv-5nT~_VsJQLwR!63B8U?hW)?=-Hdlqq`a)%WG*cKqMfqu&U6`6B@bTa*hHb`MGTvKIJRjs3NL+*6oUu`f zPz-+a;yzVqgUnl|_Ft%7(MqVuf;hXE{lHCF2ZJV3dw8A0ZK9=1GTeu=CHDQBU?IYD zYb`v2rzovi+{2bQ@h4?87jd5uw$%IJMg@8LZ1vzM6o{&c7{V%n5d_#@0$C223kja0 zjv%e6ch#8!Yiyzet6(Ps>o6M6;8nan=LVmWkAUisOgL8(UDj`QAml+b0wtTWQz})) zSJ`rn{zz=D(Z4h{djmEwSX!(^ZPaMhTGKdHXyg77DUCNG*u3gne57pNGR1|dUZ|DD zUz|F?3wuqfM>2#Z)dh{pi{q#ASe1LBs*PR_05B!hk@A>Ki}d9}v5yvdfiOihrQ8wUSumgQPT z^#CeUufkXX@5DLrvx5#hRD)I=NS3K=5*W_V>qWl{rNnBGEPPs!nOv=RtGrjq3z|oz z%TQ`338%qxgAOAc(jbx<>pSsBsbK8L>)Xq6SeSZ@BwFdhWMPA9H$=OVZ%8pZ3SwOU zve7>|_N5K7hM2X<8_siH#wcItPcL%K1u0ta&UGs3R;U zDFUi^?@j0u_Vu&Ua)bjE8WCg%lxXp`R{m?P8%2g!!Sm&i8ysliZz-Pe)W~iKi$2@- z%_3*UuodHBQkRe`Gg%(oKyxZiY$9Kkf}%9HjO|Gs??vP=@Th3JlaO^YUi*R06`J)L zM<&jp6-PabbnTBvoEC@yMN~q%Hte32CG^+Hq!Y-3#Bck`o&Ye^n)8gAcjrS3G3;f# ztlv78_U$6c{iV}g2vq6cNn)6j5UD?NVll)n<{W@3DD~vmQD0afGzl}{o*aCRADki_ z=2bm;e{nE5XBgAp9!e}Kj3yT4)qV7PJvnnErUkw1#M->mWvgOe+8O_dh*2zSE)^88 zHm|BVM?!u%g)5yXB(SvQ%{h1(*lmIK`cKw|O268HNamNIhp(p3)}H)Y zPDp#QH5Ayq^3-4%J5cMD$!OkkaoPKe-}-JTT@VzuHovho{+xMvA)b$wYN|zTDK{_A z!=;ipwz8(>5Q?(SiryT8!!Lqar~p8UnO`j=uM&6I*a>7SB%*^ANS&jk`adDWz7Sx2zfof8}0FuZtes9;}u zB+1-Zal>$baBaxDuX&9iE1ln=o-T=^!RCgr5bsJ~CbW6gB=GQPFj?(4`p2#G(oAxe zKV8Tn{kWAQX$9i_OdFVjLG*L=sG>-tI9wRH1Q$&*H~5=?sf z00n0WnNK)qk3fD%dRC{TQE?y+baCD^r9)P~=SLLO6W>vFO;58*F`ox*%F>k6!x3eP zc{T1$&hc9d;0GDo(7-vRvd2`T@-mUcE?7|-H>ONK0Yq}-H>J~aChwpa{&C^2T`ni| zz*%QM45LVV0&)-tQ>Q{NTp92^7BAbrnT{X= z{9VAVs&sD53A%Sg-2258V;u3+r`FgO<8l;^HMYd#YmI#r=S~9KckScO`lDlr5YJ*H zTi?`7<`$KC)kJX=7tUgxcLwDBKwjd8!cf(cQor`?hg6AB>D0=FrBh?)RW8VhP1ByN z)SlFH0!LQ*%68G_C6fTCp&&2fem+vRBmRkKB$Xxc=k(;|r)@Y%0}Wnp#Qlu=W?q%I zCiOVHU(Drsu?a?sn+Gsw=b_S!Z^?s&q(`@$B9FqBJoJ#Xr)3nW#N~ydM4dP7PTb(t zlMfWb={ATW2Afk+3ssZm9Am&uE$q-@f_UMx1Dod;oX)$GpGoCu2*2&EynoQJ>*{3a zoZ^Vt6|5|YO|SfVPV8Lm$x+&q!JI(%%5kuSFHH)rbqC$g2l1>Ux5m8#4#{F8PY=8VI@V4ed8Ja-K;lqb{X!#!&;aj>ZKK?0ZXiqsqd&(KwQ!=z@*^8i? z#a%onx%!-sH_EUGHPGr3#5%U+M#`Q?w}Uk52@(;DP87;v74K_x_RR*0!>X&5ktlO# zmEzeP1rG74R6Zc)k)ZLcZFSRy+?rG@s)+duS#@ktn@C|03e3*a8spHy20vtI^`9bT z_u`f)O#Ei@b@NBgI_(O!s3JdE!u(*Tcut&)y=WsL6Nwiyyej-%DU2D=c!%rQ?BN9R zn<^_3*dgnGGaw`s2nTI<@3*@soU1iqFLm{L9%O65oe^%}+Em03Ncf~gPHAW7B|LXy z0XAoQ6Q0}EOJTxui@bz$6>16rPWHPuQ*dpY}NlQP&(W~Yj6k}hp_|woF2JBV+Dt3<`-hr%Ezr=pxxW7j1 zQwQya#XN8`!r~?-DhW$G7|LP$7=SE~H0T%rEt}55mQ81YbJ9bhyDkeI2OSDJDZ<&H zfCpc7z{})0@Nt=f179eoSpdWVRPk$8P4*5(N=#E;;=Ie`upgiM9uKzS z@x}&0gFt?wmMqhh0#=h0PTsd*lS2lcL+|pf>WYJ00cC2+LrF&Ku@*@=<3Z4k@6y#! z1HMbnm)Yt|r(a~xO`^ssNf!ar*|t-Y`Oe|QKy0%RQc&v8h?=9KfjzMc^aKlRn{_^f zPOx^2NbYUce~}0pm&&~$NzXK7ifEu4c5>-SK}EYd6hM6C<_M=<>z^`Oj3k*G7N#-` zxyvde%Z#-Cp}s%T3I@_;8$>*}*5a{_4bhZ5PS`}wwZ3Xg`+J=Nw~gilc5$!BBVGAY zD&t7Tcn~`6DR*<+%e&|>X3_gVDM4CAw(lkKjiS9|fHYi7ehib9a)?dYa0xv1kYhY| zK1s8QHID&!cPqsnt$usgt_PNiBC$i=EUeC-oJTG8+^^rP-j9@t9;JJwN>$ z4<-AaP5#qrU)yC(0;$ZBDYK-ka?;jB*)PXZ=Ze?K%?i!Ktb-ew40db_8Q7VV*EtTO zdUh6LWukK?5E%5p%-dPvF~TA|IkI*G{jrh8Wn3>JB}N<@nAM*td3w9`L)w-lniZ-u zc$M{GEz?Alj4g%}{#i}WSxk1qGl~wxM_gCa>p1@eM+n3+@v-S<(TCEr%<+pqQ7xQ? zGQ;jyC|j5B74kB3+(IwtKkA%G?O`f>Qqfnj3f7$OTvI!j;|gTIK$q6|JB8Jn9_vO0 z_@W-;zA>)&S=##f=tfTy!#_^$B-!k5xF6oc-c@rjBk6M~M|wHubj3;$=AMofQ<_AOs>}JJ5>u%(%)41kNIq1IvFKc1K))za8*eVg&hY`m|wpzYQxnde<~ z0>F0FV=72u2bV~!IPY^z3hyaE&K20W0xTUoB(F?-BcLgo=QC)WAQ$vR`^$PY!pZ4@cA({mL4nip57 zdCG^p;&{{ayb!lpWN|AY_dYVga-|DRmxFPw@mJ2*&FX8R`r5DPFlu7wmpdZSrh4hXG*R{@B@?OJgoIBda|NU)=bHI zoUCH*`Sx;vs` zPpS@9wL>DBnYNtN0#XtqD+Z<19QA2O#!3`2H>av3C%Z1K->_Y=GO9r|_0?TF(ug(M zsfVgD>2Z;^IabF9Wh7QDV{@_5e`@_9uF=vT!SfDZzgBP77YHt~taOO48%DIb^uUh$ z`infoEYMh5Eqxxb9)of#dL0(3HGTkLB(HK?r`|5C7LpMKO)@-WK;T8j%OIznZiwbB>UnP8=V#ywX^ z#w%pd#G^D3+yFp;7Y+X%**j9Ug~Lnk%jW3BS_}vJqIQ=_yHuY?brm}Bto2{Fs__T8 z>m`%(QzwTF&)35W3APj?m@{JQo40Vp&ghxSY@oCQu1}i%Y^G~yrc>?!%GwSUbZPtE z`JSM$UpOC{HJjhnCYC-NJ=cy1Hhb%;Dq^GT&FVg(_S`i`KL)?`?}%Bdy1Myqr4=Ft z)m|;AP?7ZW#NlI?Tw^Wh|f_hvJC4dygPAxw|6lgr!oKdcOn%DRBs|th9xAZWd^SbKBpPvt@oi4p4n^m-7BH#T&!dE0YfwmPv zJvr9_xZ&mt8a@SddBG5X^FI&lR@2vs84pvpH}Kr*=JYUg(t6T3t2Vv*z-nBnO6}NE zd7O;h6zmPVa$?uX!^?4*Sy;-w*#D+hP*|`1P)`;;LRIC&r<+@dCU=5$4=m8#=W_95 z9$r6TS8#2ZQPdPShq=FYud1yz-Ugeq!-aNd#NHAyp792bt!@mP??z0FA2Vkw_-1e$ zFc%5V;5y)fhG@XskZJ;5K~{qJfOyyR?QP)%$eys(X!`_~u7!y9`0aNY8C#Pqn;O9) zHV(3XM>dH7)_*;5Za{8E&zB~v(*;JqJMNKpY=6-}Hh^_{2F%S6Fae{5=^|BJ@5~Db z;0P59g7!1|nqyvOS9?e&k39|Qw|(EGD!0KUe^x5=>4YiXF%YJxZn}qQ55!Upy%(K@ z<~L{lgng+3LFW)>Wk^rl5&0K-bTpl5L`;>+E#Q^(V$QsaqM_u^Eyz6-cq3@0gW47Q zgMs~Vq_Bar7K}V#VNjuQ?ySq&@jlx>);I}-OG)PvYaoGb&st}{GXTOlRh~YW`8{XK zCi!O&8%jRv05ItdVe*_@YgZf(29C$6{J#S6FL59%7jaI(AhDDH&{8WCD?)$#0*U1U zif=ejaG`mbg5nn$D88S>9m1==H>n7{S z-m<4;{-#Kz1XZOyO--#9yrgMw?PQ#+F}XR?6Uq7(IU_p z*UZ@^jji`;M$ZZU{z^LEm{a1HU~O|wvH0%FS+3Y}66jWgl5kevkUa$Fb1ZQfV^SBg z)~s7uhAeXr{66iM`zERZg8MVJTQ8v1(eKDRRM39wpb=*f=Yuiz3j0JdaH)}79jJ^bPd-8#dQb7oZ4CAoR2{*B&Yq;uo2y@+8FZ| z&34nQ-JV*`uQN$pq=D`8L=KVU&RjtdF$wI!^$qlh=Qw+LyDFS2pxOY(1!G1jS^{~Dde#<9}X zTh;FEOqiNIfN*GhA@?=5i`;6IJ_CnLzdCeZm;2I%{XJa@R#BtYy#(Fi08_?wT%6?G zN8}q53FEtj9)%%X@jGF|;@92I{Rlhb&r_+EN)QjC6Sr;n9EP5^1?f3rtY%N+B&s8Q?}lkqvyO=}aXDxXS++z+i%7g{o)&7W4e~2kZ8xiz11ICtT@a)-*m*yU3z*{=Nj2(#97} ziWm#jI2HEQwIMUdP)B#a3U7HsY_^}U<6QPH`N6RFKJh_Az5^He)_fo?j;zw zh@gUt2+okp1-!bth#+0e5xU$yV6&)&Ps#-YBe`H;R`bHC_W$92fq$`YA~b*Ib^&%F zE>!r`?E){8MTpQlJRni6ajSa4eYlkuxm}>fdS;i%iRaJzu` zVoHGjGV8n4Qnw3;Kxs9QN|dA@uvYS-CyNe3N`qGm&={u?;>Uo9I@p-VH65YTZICi} zv%tkpyYUL^T;4+5EO0h%kkdNyRjEnVspJk^EHGRpP8A3?|BsqLp_1yMJD&4*Matnt zEF})9GZ#)x%iJsQC@{dU(;I~T8|sCze8 zyG1AOj?}ipd5hImMY>ma&++yK-CC@WV^ufTU+RxU-Cfa&ZQMofY!^9?!vuk08i8-X z!H3;e0@8Arm(o~<@<_EKL~0Rf_nJq|Lj*lNz@F4CYw!}rE4LjkRbiCiR@v?34oJWG zQpoHQk>Cdit{Gem*+P}w0L6@Rhf`1;E(NGG$tfH&5ybcVbQndp_T|1j6XbW!L{L z5{)Z8}}E{XmeqjG2}{hcnqYd6KY8b0_hg z==3`dGPXA}I?Psdn8MBJeAdt7-HbEn^~c8I9Jv$g4tHbS&8T1>TH}X8vj{AB8kt=EsIb%i8orF&A`kcVoopxh&F_8Wyi|68R+Du~Bt( zb?es2VHdX>%N@iYi|=tk^C42IYA$M>dxn28V4+DGYHJ2m)ms_?Q`QmPV9OA-g=r$63(u%WQjm72$7 ze0Ht*G8#Mw+($ej>mYBcEOevu~(tx*WziE6D$ESpc{vf+36xm6@}2>cse zIlMZgm2b_sODzAo8N^7&sr4?a^S{NB;0ipkzgCP?*q_f)!xi4F-BV2~rw=afrTkX> zMyc>4D#&IrLlOydA|~`vLP_yH{^J=CSHj2YcmO0l7;c>Yn&|Iv?+l z>vkfjt)1;H{nm_c#XZ`_yGx4JJg6=*iBF(6Z_Ec&+{x-f=vUE9TBt1{aBB9|UhPTc zPM6TqWAG(!HF}DT*5ct;lo+>qhujjDJ^YmQ4HGKH`Pw_5EA~aH8T?~>3-sDHt~}`s z_dt|(V$s{e^~YItTQS?&iArlGFPV!AwhUv_ve~YhALlLLS&Po88ISOe#h9QEBIf@3 z0M`O@!p0Spjmg(R%Tr-_{P2I?6 zE)41(~C3dM|P)!0etmm?S)~ig9%2R3(F^1wW{Mn8njlaS1+%r9>fqN3|z(K z{=R=hJz-d{-7od_&M_O+kYKyz)!77>&jwoxgh)c=(0e0?hOV{I^5MZtIXFTc6&riw zw|NGeM`r5;xl}diekGFpYEC%0xG&TkDjyzhJP^A%TYv_tXdreCUTrna1=(!s==Nr+ z^h=ehU<3NY`Pq-uxm4;*qRzO%I!=WnRFyiHW~T*j^4D-fM1-5JtoF9gen2=YQAFTa zubuxI(M-*&d8bgITl>y8c*QKbdo?S@{T7|}%k0Xa8??rY_y{z)TH`}VQ_NRUu;I%E zVp=Kp=A}IiOUk{+BDK$8)R8}k=I+oFVM_(da~(Hk<03&1#-SPGwZ`}5{nBS*Mar2J zqflxGImm35Zg+7SuwrZ^8P1VQ5DC}WlAC^j!+_MUD8k4TNHQ`+y9F{dCsvzAGGm;e z#u(=gkngQl`$%2Y{jbGtVq8b=v+bdS(qrQr?q5(4J3Z7qIotBu@Pg*h^x^41gumG~ zLO#bm9qxj383g0>q;AW-ZYj=ae5BQ1(P~VS74Lb3SK7isHX69o(!N#5GDx#Z2Ju+! z;43#hTyUX=A2Roa%ie9ce=#0PyTPnjw;JVq8-LAScSGDubE!Wwcy+pv){LWh4~_-8 z`co)iZ`Pi4&#L^pYxy-?9`v^Mj?mr6@zd()%APv0vU4At(j zlsp@LJ8IrJH(2)iZVPwX8nZ(rQU08rcoxcEdcl^v<(t9}dPH=#eLW;#(FgD=6>zsf zIDvL^Q4b2+%x~KEl^H~G;ZtYW{dQt?xt{t@$~5iSD2p>zgd_f`|0_W*Rs?y=AVG4t z%HK8XhbGS_vo08TCdL7=8yzxNC@&@Q3Us*`VdbO{=6DE`KPprlAI|5z)PK>f(B?mR zX0er_&Akq7f^qc0Ex8%ueBeGsk|S;3$M?#c*7PF^K%kCr0}ai)_p?MAP@}7>n!lI7 zdO=|4+Av(oSqDO@Yr`)ONmgZNw0U0nrRk_paq&R?IB`{@)0Z$+dgo@@3t)h5>$|r= zTY^A(e{mIo3DVQ4>B4N@X33L)Qjh{&FV?;#!cF?jY)`@;2I#sF-*HgtpwJ<0CQ!(r zCh$qj8$mw%=D#z&$4+AIcnuGmuiL)VD#)|n6Q5xHmBSKeC$hTKE1cSu3SyTv`tOYA znQx^32l{xHPpNas#I7*jdXyA<%&Nhv(|=2ObuHwAfkV6-uFu@zi&%j9K{m?4T@p<{ zDBIin-1uqOvNv8yYZb2&czwn|v#CwMQt_(njX&otF!Qc=WpCs_0}^;IYWB$`tI_1l z6=V|_hAi+lcTDE>u^^*V8{WZjl>Hmc~ zud4Qj{MbT9;iS(A8eio8K7#Ij)>>6V0jP_R@5p5JLX8(S|R^)bin<3&Qf2Q-fdM;3B zw|UX(z7!dZ8;RvQ^HOdplAFr5@OL~{6k5CSHg&GO+N5IX1s-JNK|#jR1+l7Cqko|# z8Q)Yv(Y7l+#lF(J3MahWW>{jb_GDYyt8Ln9O~y)rxE9YF?oQ|0EL|rSp781D7ulSM zx@KVJE7fbc&mV907pvDkYj3xjm=@zQECfxjKKNb+r~yl|V>ud-TmRo;y1(qibYB=; zJ0zrgB;B%g(R2J1iRd2X*q#4;ne{PijDW7)|A%mHWz)&}hbyr!`G?YS>T@pKEgOmH z>1g3m!MSi#7aUD2{VJY&xk!ymv8psU0p0NDB{<#kSTGRF9VNAp|L0lZA7gh`7jv*A0o~-iX{SMpf8n=K!@o0r=sbuuu`oJEe|29ViRx#awqL9&lx8u_+ z@!Yj4o;zRoQGeXIi`3{}r8TwFP|I1APS3TwFd@mG$H9KYK0?Iyc76Aev>!wW0@k!E ze5MQRt`L7kCm+3^Qisd7v+L=p`)DT{)O}zesC$VM)QyI6@4~!mh@_fZ9!y?yn2`8u z(pP5#xewf19UhTJHg;kbtv{WcK^UYUo;1B%{6j;x6$VrC2PFkTPUyBduQZwo+P32P zLLY@I24c6*S5qskaR29)fq?C?PQZ4t${P}}t2&wPgk`pVIM41Y*2O-h)C~|XSs)#>ramEx4ajCWvW0r@? zme6R~dlbpWX){LLlK$+s`iXI78+uHIHOn%e%O{D`4wd??3y`I#f>bf<52 z4x;$**dbn0)ln)#D3V@-my3;s=YC4t$DD5SPBmf>P&mty~Xa~TEJa`D33TGJJrR1s&Z z_V1c?L*r~ka1bY=zdj^L{aLA>bxoYD2pEG>_M&#^BND6RcWLZwewT@v;P}e;ql%TM z9|<;8E{hkiHA=cL-3(_aPJfGEzq&>$xK{Rz1KNy>yCkG(g6kFvTN|L83hX(Ot6G8mRfCXYg@Ff(rQ~?S8!`sgy0Ie;ZjYlZJ!vmu~op0{J-bk z=b21Gu=ag_{q^(y{vEhE=ehemcR%;sa~WJG3uH(gFOV^Gq`*~lOM&Q4@c?B8DwJ03 z^E~v7o{p^5r?NCU4B22Yb6441;okU+RW3_dY|64Xj)v8u*Gzi8M>!<(SESc-@M_mV z+jm)kQTEeDaavkCyd7 zcv*PIk9h4jBY0cePdGc}9;KX&9d}2j_*L`%%+uBrKZV?~qEEJdrX%T#f3_~|^BKsH zQV}5)#C$R<7*~#pKO~Jr#z4;bWzeO`-$S@|jy#?gxeMg?IOlfW1F~Q5t1EH4zcAZ{>yl zn!Do*d3B%=tMID>F(0rYOw}909JXxPlvXx-9~{;XHOO9%?u>)z2w<-_*!s!+;Z5=V zpd@TId-oBN?HBrAjja{z@;FKM*v@W`?Tb++FFIgPyuTW3Z5a(G+DOFj2*%c!I6gm&sPu)rv`%3$%p8J;WdZ_xb#PsWZ%U97u#ii?3=^c9SA|t1)zbi1= zR^vw6lx8C(oErmNGnh9hBVC$heh%Td?&{Hy~(g(7P z8mdwFWBuQZSWDA|mt;46eN?WafeJ?JQQEO6R*2L+!KbW-h*{wX@CWN9fnspe^& zRJUt)wh5y_vN-|E*1B6{0Z`#tf0^t{v<|1qFnJhi-a&`c;TV{342w&{bAMY3u03^G z&2aV@={iOUoKQQM{YG|E)r&unHz=}gWmfIq5lvQ%P%<)Qi&VsjV%Z9_E}1aa-q{^( zyPU=vsV54_PIQc(K$q15N<-_hby=n8*ksv%(@YT z`^ywm-NQ`d>}6~PRc0SUpRayGHsLu<<+89@y+-s?!Nsf?yHxfyLf)^pU+HXY-dTN- z_MM&ZXLzQO3aXwRX;akGP)Cbpp3RC-QWb}isyJ5S70^JnZKBf%Da}qtN9cQ;J*{Gi z;B0#SJ({Zeil(Z}W1e|DJ`xyP-J7DSZkr#J9`vH9iree9rm7dTG9Z6gRh6g=)2gbn z*Z-OJ&t6a_;_QqG=n~+Ag9_ACWp9|!_VH(7Jyqx0daAxp9cCUiYN|Z*j?(-6J+xFk z{vuI0TB^$MuD3vd;ma1=P zPcKAz(&N%`TB^30#)O8d_E<9(%Ba}(?x&0d-L+LMZTr+%Mrx~CYP415X>C<`+q|?a zsZPBQ>P=gf-pssg&1R#+u+gQh3iVduUC<&p#-!bgwkkVx4539>@kFYs3cIPQdI(tp zVVCt#RaL0h(pDWilrB|O!u4I%K2ZY>OJy2u9}~`~PTr`ik{!^m@6}T`Jt=Gb!Bv-Q zbyb(>ZPj+6gPqyMB%qrnc`!<-Bmi;BZphQHfB`{vL`T=La-#J}PMN@&uEm?JwQ4$^ zB6MA~?~pnBOI29)Cj@iQdkJlEV4@AmC`Rfhv%febwtc_=!O)Q0_9qZgVRc9>aPo+j zs$NxCJ%o=Fs<8S2ju9%XHp*u?bTCS(zA2w<%I!}Xow}>Ax*VG(pV#=F&xd5%=$({_ zQj0gOGW#E+!b)=~tY&sM(5&q_hI6BBimj{O+UNp1>Z=g(^E4t|tU|{)Yw>F#jqcj3 z{B5j=S-a>hj=$|`omEkX)vNX@z1v|SC=@i>tCqCM5lnc~gH|kO(^Dtj{u%96i;2|T zevw4oK9|3)_AIHFI9M{Gy=tnXx~f75<7{}|HYGEQieza@v>`1RCd))kj4stxM}=w# zsrF&j78jg#ycVmS{w^(6i`GhKz5PU5tgP>F=3=i{&%a4(v@<*Xu3alFDHqJ@ygTo2yml~HLyoN zi`qP4NBeo%JU|@U`-m$U#u|4IzHmkPN+?rb4zm^~w@>OpvOs|-EHhf}gz zVR>kJ5Cm<`uy(rWkvHKW?JZ`&@x_imzSujX5WtEk_LEMrO~l0BmQCN{9-HT3WUA!l zn1jKO{D^#Ur>(O^;^oMCeRPs=HaFl82l+K3mKgzOurL9Q@horcg_$yhIQ#Isxp zle>zYDHmUguVSBeTdmXpNL@+6XqXZI93pA@MAEIZ{^duL_x(md=SX3igA4Y&y^N2zwh!*J33~ ziMY+t82jA)*pPFs297w$X+3=NF@XgV!EG{zp;Er7+7+1OFaAK&LS)UKe@4g=C!ye$ z!oqw>ri>52ujQgIlABaW$@`mz&yl!-4-m1|Pf3(_ApVipIPMD4;qjrpv87L$JEw*+ zS-s1~cHI}uYoxZU{f#258cG^O&aHVSMmKodVKQvjKT>+(Ge}`ibf%m`1);yqTqMj} zK4T;YveJBJqy~>T$OjYlV&yNkq?F}P3yC_Ul$<%DCWfiD#Tqg~8WFd$xb5@DuL(~1 z^#Sd1XQ4J9fyanAOAL(WDuY|}V&^7XKfI>16UEp^Sn5%7Bmo-dBqN|nn~+=h(%<|c z*SZY-AjX9HRjDz-aiJ{lEHCQC11Ymc3FtR#w1Bu-D(eRb_FI49+~XM{lkO)pkT}pC zKu_mB&?WjnQ};|G!{3cITyWwR?46IxSc$y9Tq;6>i7C$?+O%2POX#T?Gq{h~bbYgY z@!o}8@_Wzu=H=!X+@nR9SoYa6S>}a&Zdd_mALaw;%-CR3USqBsb!wk$Fd?$c(z*ZgJO4CKn1LyvCd zE9lu1~A_lJqhsi*}FsNpRhl#m^Aa2vrXxGMQ6#e}ra*+570)b|b_`z@SL`P^QwqFoi zU8V{Y$Qa=!bX~*{L2XiF&sz6NP%}i-b`23%jn;G215qjF~p89@W=ICI5n5pk)Jv7>LOEX)$ zki~kaGY5aXoV_u6L!7^Jujiqu;_{sJQm&pI2KMxTYgWVIz%X_Xzs{;V<_+}WZ{Oe@ z5=q}Z=ONMoPvq&Thar=v;g95^E|c@ay3D>o9!uNR{-L&)wV~V$;dP&xVag&`kP$ z_QWlv43cHmF747h0`quh**()6IB#a(z#Is2mgfof3VxwZC#B$#o{eO9moB^nwCT{E zfD;7SC3czy2<%-V)nU>>kWZ)6HV8X?$%RW%WATY@# zgvUbDp9A9=t(>>9Trv0TWoUb4PwYncChS);7D;;>F$&-Q##yfk4;6t?D2uLk7}N4b zlwa?i;HJY4bxxTcm#uYifH@l`u>OtoXMR|_)L+cGu^*K~wHKil|3iP~ff}ayr>t>L z;@?a;8F@{-AsdcYPbc=-)e2(G)&*^xHIl6OsPg9Q#t|Oy_Gr4SP=W3y8(H1xPrNqB z;(e%vdTC&i^)%?76gtFI%$cz)EA^y&IE=j~lWGP6iUQO92R_p)p={nyL30CEX?oJ_ zOzB6o%#2jzMbg19KmyU89ep|m9bAI3G}UXPityU#g$26XC&=a9pVo@7%13(s{2BIK zHE73y+4NSv%qT}uD;yClb`E6}I!o@z$lN8>?B#CTw*rK1npFqrU9X6ql$lUjzea|; z+=N^56~mcZc>YlA-M5e)V@kbr|-c!U+6=&ZF_U9RBW=FR=671 z9?IIVc8R}nZAVVSvjKPG+M~XQliTC68%vL7Z)9x9KV&^JR~n{g{i(3}waCT#j$rbU zJt`}XA!J6*p+Iy_{1>6;jQ$MR*s9q#W*({j_BWW z*U8zFY*btD&oOWvAo3VEJJiuWH0$slcfd`OiX`9ni2!9*J8~Hvq5MLgL2C9rP8IR? zRdQgW{23#EhRPpL{U=$$hMdff&?}x>c5?n7I)HZC&`a%coQ<_dgF19Xj+6|+v?ogovVvn4w9_vgQoKGHGtTB|qdh>e}B%|#|&{rSa#^c6@@d6V~_LoKT zJllS5)g7{4BMwU6+L`hWR;=}YX?+W;y()>)wBPQ_d@|U_SND8YdtXuU5CiJ=hZePl z60AXWgwz>+jXk8vuq~#}Tk|>bM5XB7Fy_6}V&bM*zSpSBc{hsx* z49{tR#q|rCny=yGKrob$gF=j_I<4^t>NMuGNUaXF`jEkO8R9#TPewX9fozitWN52u zTJ)mH!}7+pFIql!oDgKl^7^$eo)k>xVnz%8zndlJDxHDd#4gjc^;9d24J__AL3I{J zlZ8j5M{ienU;npYQYh!pn4Q6xgb&-J5;~~#oiz73vt*SSIF;=bU^HJ*x;tb6M)4J+ z^j0fI1xI9W$XU`pWV^g+XSbMmZs06wkCEZV^kjs+XhS|8pUV!dZEjrK;#vPwu|PtP zvNn&|L5wQP(;#Akg4PA9IrdpEOi6vWp+=C*KV6mVtN%Ras)_uKY_0zn>GhUb$C#XgCs79%uo<^bz9l^Fg+6P0 zkzCA@`~*kpv>BDG^tbF3Qb<9_rMF{F)&>~Y_F0rZu!@pzK|h&4)t8 znnHOR{%$OFt#?c}1q+_jCK|6GhUD7!xD+jvkXyW)u-rh5ZONIi+sZsuw;49LvgnF# z&B=W4y4Tv#WxlrAZu7+n*&9naF_1Ryt9$1`PHihPR$HW4OMwAJ^|yYtp<*SF4w>HypQ?1Xw6K*2b{e%eZ(gGp%9@*K#HV|)tS9v38 z6?#p5M|NCC1S!lD|lnbb=G&6jm9m2FO z|1J4Hi0IFlx*AaeiTaCu510{lIxBQ*GfpBn4s+^x>$~C)sY&~WX9J%sWt|(I z`O(AQXphbd{hr&M8Dp=T$(1-6>m=aUbS#|#9c6xGlv&-QJmbrwr)avT&b;tHG?u8DGWYjHP3}*Pi2Vsu(+#OQ@>`a~W0csd14u&hrowoz1X4+WRq3 zleJf@EnEf(wTLd-$C35yd@_^JYxa5`-qW7tFPd>+=# z$Mg-{RW#$c<&Ek7`Z(CQdZ+XX*|W}=DJ7@*i@0HSi4;;R=HpEsvsrT9vJUT;e)~OS zni0MsSORjdIUxE55;=Z8*e=0IM63T0*6Q|e>AhI}K9_$+QVFX&dLe6Bn|IQs>wJ-| zBotP(xeKGU&>Rd56gi-N*)SN!(YXULh!u=7d%Hr}#+K>PArA>v$u1f?S&g^KiAn5o zIWf7cHD^Zgpx_wUlK1gE1OcM6GfI!@3lkmoA%Z+hlDhBNvOp%jXDb@>}V@1N_D7B(R?s zdU<|rg)86f-V+^Gk0$Gi}*&?0`6a2LTD zJI}x4-DL0?;FE296!;Kh9p7*`xE-d7i_XR0WBTtG`tRrZ?`Qh&r~2yHO~#8%uPK1HsL%_q6bS${OZwaRKaA&}0M`Jw0AF+etMWz42&;qb&| zAE{LkPg^VWqTnk`!Tm>ITv2co4(6SioSWHlHIH(eLdW~Vgwkby^HIC(!a$UHo&iwp zjdsdkEMuk|bp-l3<=>SI=izl3bSfir6Fy=^e=-CRHJ*W)p`2=RM8;v@a2N}ZiNTm! zOOUeYt+begR$1P3&}{+ye^Atu?V5*E8p#(`m9y< zb;&1akruWdkk}f=%1SC5Rzx#UJ7+W8 zWRbxP9OV!KG~Exr1w7AiJJa~w%%`X*dl`4H)&cJVs0qWhQ%12|Oi_Q6urY=k4K4ZstiwB^m>oh`)LT*Z%PWU>!~~LzRg8X%B}UY>>}ZP(USyDH zc-Od#!V+6$3(r@!#>sM<8`HbAz82EZ35W)lzl$XbT;%5&$#BjO)Y0eSWpzDUBFqad zjF(lI*Wc)C%@Z{)q3n3>IWL6kA$nbW9atU>zDQyt+rGgl92wsx&LZWpw3-LE5ux&= z#>9J4v*WY;>vq)fO*UXrwuz5zS$yY(5>0w}o?U%0GXLkrCre_feC8&LU8>l5#V(C( zWr=;O*jr+6GKK;OY&*pEXz*9L>nuqD=@S8-ddZ~GB(t5$Jih$UU{h{1igCJEkiT=E zQ%Aaj{Pk^75tXDX2)meYB{>yT&{aY8ZEm5dCY&o6uAn$mK^*dgllY4DlO2ClDA7T} zQbDQIMY2>7gd1d%@gdCEKlqZa9v1iA%d6{$+4E{sKh%X(OSqa${p^USpFBG~q3=br=F%riMN739XU|CiOzBh-&#iTr zmeq48*KJ+%HR=5qBwODwNUBw45U+K)LDH;?4U%rtyF`QSssIASbYpqZGCZxPJEU1kw!v7Gs`mg2EpGj_$I;k8(hX0Yq!BS3%7<|9r)doK#c!|MV1z%!tOYl5{cL<(k@S}oH zGq`Yrtu%wX1s`s3{Qyj|!BfRP#^7GTk1i1+m?vf4Gq`@yrPbgW;^#$!%fj1gF}U1; zwH`CLJP2cLHF&k)KR5U)!EZBoo!~bbe1qV12Hzxjz~HwDUS{wz!Iv6*i{J$Y-zs>v z!M6#XVen?bPd9jr;9i687krSxHw*4I_#weRU#!dCDtL#%Ey3S0c!%JJ41QGbXABO< zR9VdimuI`J2MnGp_!fhw3Vyr6y@GEtc$(l122U4!mBBLvuP`{QSY;I&+%Nb-gBJ+y zH~134XBxav@N|Qh2|m`~)q#8tO_fHx-Y=jmH!d)QimkV-sy`(y(zG zn-3RBu`l2S!K7n1=xn}aY%;L<$k;q-j?C1ieG>kSq|d7-Cd4K!?{Yxc%Leb3$*yqKHjM77v|WJerfgMZ%CwH-dc zX;9zg>)!74EMNEOQP0&+vj|3sBTZyy@OQb7INRsE=!5?H4hn|mx~V&J*Y67KZTI+x zvEe(^xeLytta8{ek7tuS#@;XwlMS}Dio_aWRp#ELByibxJkiatelP`ak)V~`YSWy3NOkh&|yL|$KJD&j$KjJV1E{YqKx(^^OzN!8*cc6d$ zX9M8|1H0p*>bEuoQ~p zj8IY|M?0Yd@EE+I*mdC1Etv<_p2nk!T2u24n+brBN{gG97m>yHhLV=xsr?1(RnC8M z8)L?jvp8~g5`x>mbK^PlEsjIKCuxPAM@MjbY=~<}FJ->P!&PLtFIo1iPo)XvHR}9k zzU9$u$?Qg*%eF6M19?>Mfc>7?`~A`TQ2|)fU;JD|-i1}v96U+$jG8WH8hyDYSKOvcxr9gL-+`{B zrr}5Rk^b`&iM26S6l0;`t20F|H~HbfH}T?H%6-PMSUbKcFR z81cflrNl=)>t7PGG$sAaFZ9dT^pfu7Y51;mt)`S~aL}c>LozH5*XTaSUGu-5u6_8m z4>)+S*Ai)G$|~_FchR3W?#W^I<=TCTohiwVzZDWsV{9s(&}|)x^$5}rqz?!>{o^Dwa$C!grV3o9vo=$Lgp%IBNkB(u z%IP|(R#C|{QxZC>^JM|BSK;yb^eb?3@h3yG`C#LJOf0_67x5Bzm^%VUW1|%yg#(^Y z(mIJV^ZCFu-pvw$G5nm0T(4m~j>JQm?O|YN%7eBC_R#YB7=A)YBI4Yc@*~?NnQI5I znNW15z0gjY9ahiv48usxvYph53A*~8(9C(zhxUuAG_s-p91ME#!0Q$JSe%fv0pf`Iy`k-vUY&tiPqL?X zvbdHFYS-%QRTNw0a;_E}ofZE#A@+KUZ!$4dp*1|c4o(ssj&>wkjNm~aX$iNMcV14@ZI|{H zteO#9yn&@U{r+j|$KTficN6^epS51~xY&fSu_`(9-m4Oc$sEe1%lMrkgUjW+tc!5e zgK{8^X`#jX1dbAKLcU~WI1ZN@hgR(%0-TSU^Zzg(+AFW7aED6TPGE$v?$2xWANhN3 zW^=8_`jB8w;_b6g-wYRiU%+k67$s$3wB$Xs=d4%s)FPu#V6f=L>+hd{RBmFN6nK~Q zA^ONfNwq$`Yr+CA|pKr0h>E5yX|AZ((`Y_fSPl*yW&O<`6hpr$o84=fePl5_C zaAEblI|_9p=={%tjKW&}Qy)B05hJb3$n&TS>r9<>y=?g_8$~(U+kv0F5JIzmL=C|Y zZ)J4f@p-JT{x2itfeVp|Ey%yJbBS+bz>^`fePLGA;jI0~kn)bwvfi#>U*yiT&fXvT z4rhDNs-1*Z?WeU??I8oHfTyh&-;zr7G(5#-l0>GH$oZj|R=mf_>Gl0sTV>q8Vl3wn zdnv2JW@#f$u?hH`amgUb2{IfW&n>$;Q@%~zNn~pY1t+^N;^&?Q*%BichZ7V)-sAVM z`bpKsGH=pT&i!vuH0x=%)GL8)31qNbEr*FT7eaVPc5%> zpSU6JKHQejp@j%9+xp|%wukSC2Lw+t^xt&FptzLtz_Eqqf~G!ooqABDH)4e{92UxX zMrX>|0LWzQKOtB?ny+XZb^=4+M+5=f4>c;9Ej z7tu5vdBuH+=f+sr}mV#cafb!(7!3=m#mFD z_fnX*eH*epc{IzneS5Rx3ZQ|aZ|1dqqFdH!WBEMP_8uSFwjBftUrA^ogl_n>2W*^$!WUD&UoL(n6bH?yJyA+6E+Oy7Cl-d z*t+q5LmxrcebPxks(H>oiW7E!(|QSy3YqK)OrF`)cT>_IS*7|zi958qAz7j8nwEO^ z`gOEPNKGP&=L73boh(8E8x%Eb4b zzCsCqKgN_WpON=OB|MFS^ekbfl(0Vzx?I)bW1CPw`Y4B_T@^LCdx;WhZE~8UMWaMK z%03I?P-P1wuh|pXqop@jPoOUXq#rLL1;pD$P4W*WphWe+QQnqt>cn*J%P0?e1f6Rp^+8hqunvz;&Sx6HQKa3hu^Pxm{_Jlp?Umh)V2_!_b2+z(u zcHOpiR_segNsE@x6z*V}0y7Ty&>(SrGz8JD28qn_-zOuCpD~#2Ct1kRYrW2tIXVZ7^q;c=qU}w6z5VCR3nEV6wuJZbuMb_Fh^uaF_0jc?m?bbGyY)f%N3*m#X-rb81yl(n$b5OyH4h^jj z?;S>*F8#NTsyxwu`zS6w^xr;oqkHS{Nd33A(yL}}@yzu+)X;Z7uD%@>8n5(9>nI8; zWWMo*T3Et*8j8u8h>G9nHgK8^|8CpAX~WxX*gzIUq%yV^w8t3upxNUace9#R_-3US>Dy7DPR zH-)(8{clrsI!>Z{|SY-y7{zE zl2~;tT?%o}JK8P^aRFh4xZp84q4Rh&3#GaLe^7{f&ql_}6Dq_-9x>@zw!oTrkqU9s zhtdxIM+$LoB3j;6PL+6iQ;54@oX!^J)DhX;)xaF))?PH z#uF>V{p6=%Li-~X;(l_LPRdb;YgD_+(m1RU_xThA%r=hJ8gZwykYvIM#QW-x#-WCr zrP-G&$h~>GS!8~hg4|gsU@Z$w;;*A1cN5oL-cM+6tUJ4cI~AQfkN}=GnIX}UEB2_!we3-nJ4x(IQ1C9W+|zKfKvd)o z7Kn=6egaXE+eaX(9OYh;s5dHBKPasgRLU>A}1PDexrbo}5QDqzeS^fby<-qp+v|cr^tiSI#wx0<1w^RUtBPDx8gX9O_ES7s zPhJ*YIbNG>tH}N4;mG?&EYL;JRWuG~upaoiA1cE%;+@V$9agpqUSN2^Q-L6iU zbJBmXKT0Ncwkei{jHg-6x4{Sz-MCj}&dMaM+RARaakH`NZGR*eT+%3S#Qtc2eh0L$EcL`h|cCwTyo7meir45qW_ypeM~7y_JZ z!o4-OO5no44Mw7whm8*g&6N^i6-SLi^G4f7iHoo3`o5hAKhi0$yDG)Hg>ww&z#wln z-Dp=k3PBe!lIOQtcTY99OMLa;9Hcz!g{{VA#ti*NEh@III$w@_28a+m&$Pf=7e4g2 zzD+Ychgi++4r?lC-P)rnq~tnE_!fw4nd>A+^}7o%mwhrZr4v)|RLez(rprgOeS6d= zO?WMLNMwkL2;H`bZ@5+L_4@3MX8XmI5|qfxsj}$AfKM?%H|l})Yttw(<>zSf^}rqQ^MA}coYYVK(Q7>GhiUuc z${xCjvd`w&MIU}pfKRhb;XMsMXINmy2i-}^sUw=|1pn$$98FRi2rB9+R;a;6~fxl?~TJ;rMl$xRda5T${3Oy zd3HcHr@kNhl%wU)@8x_Z#hQLecs%;xTy`Fx5_w)|6e>%MdX`6KVIhaWG3nCOEP4Zc zd-0UnYP0|^pHUX&4^3ZECd?_G@4IEMKXdwgzJgU;s0@9;twqtX(*89#du}e1&FB~W zxU)H|w`<`#p%2|cPDbPn;=b1QYjjo68JYvb{1g7l*k-L~rzh%nWP=ro;f$?0Xia_J z-#8hPuJSide|3d)9@zT7Aa5Lph|XG?eXhijZ9Vz`F*e5TE`nKf_5H%GU%lG8>pso5 zueQ!u;?O`358-y-b@osD&mp!Lj`!Y@q{lS*-PTEUI?{PM<>mmKq%`PIU@{W)YAs0C z$Jc33XWO2BVmwWd&(H_br*8Cz`s7b|&mTILd*BOsAgwyT7?G^zK+Y3F`h3yTwO=aW zy#Hbv=Bh?;sNA5NJ!4v#r{NBKfF^>lzq zb$pN|ZU^7_g)Bk$*;kFFs=e0BnN0oS?Gody?T2{karT%c2aoy=41CE?U`<+E@hn+O zlbdqBhBeV6f+J~4DPrg4v@DAOSKpi)vqz59DP*iZW$o<_9b-s=3?DLb$R**>0pE6R zH?fFs=9V4@q$r^4b<9J@lzrO!?$l0sSMxj<5-Zb>m|=n?NT2|_D0xvAH7I0QtdNQO zJ(_tKvOPELAeGLPRQL_P-^s+nJ=g@#ux^GYXpUE{ZwY%4mtMy` zdD-kT#=b{X9jwOZtT&0DvoK!6%*}kuA9^XrlfM`1d(0Ud7u{|%Ik|RN`|DOdG1q6r z1{16?I=LhQ`+2%b^zuJvamYnhSH{cONPldZdayI)YQEYRt-cIG5jmdDW*H}iH2NvA zXgf!$iFMgbydF8^ABJ4ZTij0d*P{@5ob|{8DVHQnpw}3AsEltK@!{1nR%n)CuKi>d2T@PY-k9ymfU~yL<&J9ht@~pg zsbzbf*zY^=DK|Z`I8|Q)#5N!|KM<`AqzObvgjXQiA^fxJ@?7pZ4#J-1X1&T-$G6IG zwWs&6zh2u%wWs3C<-V>x*>NWm*ksh9a3>h2b<*&_(vjDOHIGxx3MDOMLMqg4%m2u< zG{pMJd}m0u7SG_YTUf2_@uAq!aCI78P`uu`56<9JF*em1t$8(4-nZr^QMU)K7yX6e z$OG3;c^em`w#}qp_VU1WdywMw^1$`3MHICA1J`3eavIco(vn!eGQfG;himmbayZOd zF+21mmL+5T*2{mEFA5+U{qO65&=u9G-(S%t(!U9u$k=_u#4Agc&UD^ zGa+fiXkX27H zll;60td$0~ShuqcVcI}V-QM<8lXBOjVC{hjqV&=bm-9K2MXRc$TmK#(B`Ad84-00! zBIKOUPopJ*M<^S2;j|FIWpNa_G4`${Qu5t?qnCl{`BrVg&HY3nNT5$=N+?!)N!!&q z&I0Wm_pbgc>~fOi&LgRM{h@bR*%w$JOb}s2b~jwpjC9GeUhL@tStLxM^@#0~9vNmk z!=bWPtm!2>Ct{ZaWhL_dg=sbxtI`?UY(s{cWdi36hm`YjV#_nu1YR2SRS^ z!Fzhk4da8dp7>^OPI}yycYu#0iI%6cHuUPGL#>Q(>QOw_6w1nva1Rr@{_#58*rSS#BR!2%5`H^JUW8LYM5t6CBi-t*er=)B!pCRzmQ8EXmAzy>l%Hj7up{f%TBR9RMK}mW|MUBQmIAG3NCQ{u z0~@L-=DVK_(`hN3LD;F!`p258yoJnVXF-f+t5AL#Gh)z(``7@hIuwzYQrmR zc)bmOXu~vFnD85H!#*~A?<`~gk?l`SGvA3e9BadwHoVY=SJ-fa4R5#MRvSKL!#8dC zfenw@aKLnv&M7v$(1wLJth8Z+4R5yLW*gpX!-s6R(}pkF@NFA**zi*u#-C}@_1f@s z8=hms`8NEz4XbUq!G@b`xY>sH+VBY*9d$J8PZ0NV)*KN4UhBw&odp7*J z4Ii-K9vi-9!)bOs>dNKMGj=^bWWz&Fy*eIF05^{lrEW?MDl)L}pn=caZD7w}?$3;U z-6_4hNBVaqeXvZvWhs-7X+5lf9K$B+5tt0KOO70fdIn~UFN*aWqGWIRR0(`9SQqm;?N zf}WCJu0`s6O4%h}PJRrmb5 z_^R#UZ!!5O(IxNhvJl^;5x(=Gab-l<1-N(rmV7wrDq5MOr<93bz9l{>hr}cKmhh~6 z{AaIRd3J5ML6z`3-J8$PE68eo_##~X9U$&QBAml&o8Rf zpQNiuOA)`st%y_N!&DM}wIVKwN6jr=rU;`J6a|7cB{=Y#TT^ah(4{O`Qycz*UZo|K zr4bejgXSy0s#5z}5VT=YK;n_`5=P-q;YZ;vNhnuTbWCiYICtOpgv6wNp5*=m1`bLY zJS27KNyCPZIC-RZ)aWr|$DJ}h?bOpIoIY{Vz5Z6Eh{c5UB05M{E90pR#sM3f1{>0 z5WMQ@RjaT0=9;zFUZ>_%)#R)y4;0i?6_-lwuB0s$Q};Erf>Je!mQ1^kQj$ap5>jf{=b z56da_3cf0J|1H;JTV!0~UQU|jxL5G^8rz@ro_O86O#I@n1ovX?Ek%|D6Jgeb?QlKSvM87ZZSbtSekQhK$|E6Kmfdw^aorI%W)CB_Qvr%Ely zPU4d~bxJ1VQx}~kYC5eXZ5dN#%<-x;W`ttCYSgKGEhoN8zNO5PC$W*1AoP?H9Z#uB zokwXwW)6_@Nehb%nXU6Aqp9R;lCE88PfmSL3DqbeZN0_i)ooDPv6H7R z`c6@2h2wMb^VRC}YSQXG#op`G&|wOrhLiuVo}Tn9>9hZx^rnZ?tEP>bHgFYj)extw zIx3*r@jc1un_U!h@;@yc-&fE7<>Xw}N~=gWKpz$gIbYHuom%Wl&8hD*)QoU?z14RW zwJP;xMndV|ReH3LQL~gWQbw&(9fQ-39B9gOMvwL+xsn)Vd@y5MC@_T%IE1|lKfkF|&gSBdxJJjbsld zzrtj*-;$G6{j?eC%Xx7YqY$^PD&X#8`vLjSVtZ@HWyzm5ds&J_Ut+hTu@w7*;9jl0+WuC~8N z+23_;()`k9?#x3GPbjc&-~JeK}L)U`k?&MDuWdjps?}#aHhxMYIGmf zCn`B6CnqOXe$&&5OFVir3YNsV)miE3iwoeNd%e1exeLn*`6;!kdKEu6K6rV-?FP8{ zC!hcMK>_b^|I!!-&A;Q_j<@ksGhgz_+~wSSQ@T(7$RMZxp=D*v4D z-v6|L>tB@XtNnArAK#+?S(|^<10RkcF}imB>egLf-?09MZ*6GY7`n0Prf+Zh&duMw z<<{?g|F$3e@JF}*_$NQze8-(X`}r^Kx_iqne|68jzy8f{xBl0C_doF9Ll1A;{>Y<` zJ^sY+ns@Bnwfo6Edt3HB_4G5(KKK0o0|#Gt@uinvIrQplufOs8H{WXg!`pv+=TCqB zi`DjS`+M(y@YjwH|MvHfK0bWp=qI0k_BpC+{>KcO6Ek4G5`*U7UH*S}`u}74|04$3 ziQP4W?B8AfSk8mxfZq9y;9F$LoF6iZ-M*Xnj$BLJ)Z?4mzunw7_4wuvcsKW(dwhSl z$G1FL8JV6uYZ>`1(kHT}ZpO$-{CTAguW@mCWl7c53j#%fa`>UxFRCrAnYZkU(&9jF z*`q0Mc+_&!}WE8Vq;m+tzW+$!l$R#71V7|Zk0AZqhN6z z>opd21qB-j>P@TLP)8`mvaYPG%X6^@^t?zN?XK!meeS#+g*)&@!_eR(BCFW1F#!gsk>1p~c#u=CgD4_bbS zzeUuG!zXcg%f-};a3_RUA-hr8K?uJ?ILLQ+pNIj<;)4aPup!stnXrRd~ya zDoZL#YrH+n*;RilN&{41dB9s-RZ{A$TJEiOc=Zy~B+^}laek9&Kegm&GVMTeF&Q`6 z)jPkORn>Gb(=trW6Yt8E6X0`$Usb$wOqb8}>qxrm+(r5?Db-CO(vLS-D}-6JaPCBN zVjSsTr#yblcyEzi3TZ`=p-JI*|D(o3+KP&*t0iIy-J>}eq8%5mdyV!;rI&PyYE}fL z!fU;0rB^Xhl`r>}uB;BMKJ_1`w~VG{4`M}Rw77`Y;524wu-=uWE351y!O?b49IZ!G z>4#o*ydC_r1=$O3T{GeF-?yBX^Mk`lj~;vLYw0eEI_K=AGC$QWy_iP0dMW2+GEvno ztu0?!T~T_uGY&5;DX$GI4V*b`Qgw+Lhz*%e_*dfYKhUiPmL#fy(-PFc`JVkr%?Z_S z%rWu;cY2k25|bqY{rsNtD)lDD`R;#Gj5=w`;OdmZLFp1k;@dY$slQ{sW`}VNjaNeh zNopu*3|*L@hEC(VCZ&1k#H8sXcYD;ZKtDC4B#HDBm1k;vO`q17{ZYcqSi>9$aK*={ zc*5XP?MiT|1WM)_6t4zN^Qb{nk~{jfChm`Kc2~z0_9^HuY3(MB0I;MlX}Q(V`6>II zytSOJ)E_VbCvUv(5kq|ahsUbnvs0T*NtAN@Z|uz2brSq&?pKBo0k!)_k5e?W6`fh#p$rBZLH)LSZbkUC%6 zSN9*(M-3`*QwMQU2fDpTxpHSJwFDC`SDz@=XMWU|){ErtGH%9vgn7r#PZaF4AsFYo zHyRe7%Xu-zNvnVVKB_-?>_0_XaD1Udt9!DPdLHxFFGz@AU)`Sis`&YR!uj6j<4k?F zQbRvC(1o6)L|1?1@+K;8Nq^;Cn5?|e#alDHMYWcpDQj(#kqc@`;E{~o8&%x%-G@%@t4 zZify%esd{8`b!yWoIFS!)kLKa9qA@b_Tn{N{Ym@RUni3*Pi z*Oe%BD`usgrpcG-A5I&c%QB(>v%&UL3NH6Iw?yW13TrdLxd&{Xi z1Z14Bavf_KCLDG^j2bX4Ne#F;p}?j4qutMj$D2B&Zim-&)t^JF*RMb`(3L2N?VgA9 zp%WA6D;KF@3k&Ek^VBfc`O4HhnOVblL8e^86V&iPD(zzk?PIVS?i!#>uf$D{iS%#k zb13y`_wVNZCuldnLJs9*1ZA9dWBNP&yu=<)=cjZ;_V?v1xqgNDi=FR@;JYwG>^|U1 zajO)@mK4U86xveCl>W{AkGI?J(BWq=>i>Y5;)K`vC+!l(*@fY8w%OGq|1KF{Ih1e> zaWlsERYMj6skoRm1Nj|E>M^dzzD~6AKg4<7vbFWlUo18OFRcY|4-h zLpxLF(oeRs6M7rtJ|-~{mmaGaqsUL{G`C8fV)sQU7jaO=Rx`VGjSWBk9%BQhD-Oa@ zC#lp)Ds&-^>Y?cgYUH%L)JWIus{3q1qSW>N7}6djeX}2ZGl{;Ls0Q7fT&-!bFrG1h zaey(v_+j26e}l;1p!v2R>d?curTyss>el_Wuh5P$$*F_ITTyR_DWDDny2i$Lh+95aM;2Ttu*(=%LpIGl%Y{gmgvglZ>USHCFLZ%Vv)(e0)u>`AZ3pI2%J zM%s$N{zKwvgRC_e2Zqca*x|GWhenGIDD_9oqc)99AB$K=F#kGzOyb;gkn!mSrCxPt zdNO1E%?Yi2_s2EIR>u@Z7eu8CO}l8(HNOu%GeM1;_KoOquI16awJGl~^7|$2_6My> zJ&keN?TO~TEB~O>Z!yl?XWDWJZTV}xw&fPatuIS=`}<10k8#pVm~)T#81>lyP;k5VVO8qHdferUe&1l`l!_)F}g66srs z^UeCuH8N3+4D?qcOOol+{nW^=G2dS6bQ?cfSp%IYudR~Tp;Hso=s>A!bV-S8^t58v zXxGz7)@6QM zrV8#-&5pb~Ulw+oqq_XqUN!iSe7vE{f8^s09sak;$B%SHii0+};JeN-{GmK{)Qi=G zm<6T6AS@^flr2`*@)gOgg?nc>xN3`{{{b*X*tc{w}+L*u_QVfw@&R z3t%)y6x>0Nv!l^KXP`BFU4aekD>Pi!;#1xt_TfT*hog?g9rEU?5EC__%Kb0~_J{PX8 zE>)T0I;X0#wyL6ZPN1g3#8RU!)%L-f8ki>83 zj#*S$rkg}b&Z=TWzX=Zkh*YWjrJN^pj*8B$%`ROQT(P3Grl6*@7GkJVV&(@bE-t5% ziYgXW!nb0-Gg9pGs;aIGR?mf1E(wrnVG5;+%bcQWO89(N@`42punm8KtTHlJ;YI8{#E8#scxLDh2n=VTL+@7t?@rvs7y&4dY@6qz+O86{UfmROHZWK}9L@ z{F9^e=HwSu(~4eHm z>RPTqEG#FTT1inb^=*565sSsj7oAsCRFYS|tcEKOl=?N@2IiLO_3<~_LlMN!&ee&RkDtBlgoV z^39a1zd26P-%M*d%zWE^femGLk@zpcNZKrZb-0y4FNUc}4acy+)cKcki2pi_M`QpfRX$lAEPCLe`0^%0hIjx93$!7jS+tjW28*aVZ{9vjJT&l6rqn8q07Ja zmwdvXN!NSA-@i6r|F>d4vGASA!HI>x{%_^*U!Tqin}9t_pRfsd|MhwMH>B{tyh#+~ znDv({Dn<_=`)vOY;s5zN-?{T7^`|?nJ2~j=@e9X)?HxMAMNB9cz4rCjyz27Tu6S)q z58sT(FC2Qa^%JGexYmS3RaWPm2w#5t-buC%vurrih8Z@TX2WzFrrFSI!&Do(ZFsbg zq4Rq-Y_;JVHauj*7j3xThR@ir#fH0W*lfecY`D#a57=<44Y%0vHXGh(!v-5V@vpJJ z12(L%VWAC|*wAmo3>&7~@N^q`ZRob)(O6UNzD)S82s(Gz_LdD>ZFtCr`)$}_!)6<9 zwc%zPZnEJj8y4EIz=jz%Ot)d04ZSu@wPCUi-8NJ67^?HGPnht$A)*?=`K|O{LVnuoY>z2TssI^0Ps5CKFk~7 z&j6E9R9ctjQiFiYFk8mDR0%L`2)ujz2%N`-=uO}Sz@=>5mx2pCG*YPtzy-dIkvNr? z^BzpW7?<(_zrZX6SED%3!bn;HVC-n(#NG|e!PJqi==^LH96vV#Cyp_AI&kh-(!#$V z*ou*~1b%OvDeq<=dcbs8fp=rX&lX_9cw?UkoMq!J!23@{R~d0W0PMtkB>6c_snalu z{G1LfJ{=x`&;*z;k>Y_T0#C&hh#%nBXaq~ZmjZWUq%6CE?_wkm9|6xzM=lThEZ{dW zLgzKWUt`42R^Z4plzNPp8@<4DFcNWNV zux2J@!A}4;->+am1XP&M*H9i5q}Ku zo3qhD1il7%6GrmC3HTbDjxy{;R_WCo@+mlQyB`@O@W+4y&nHgsrNA{92`lh+8yEOC zM)IaEpqerJ@t+R#V-A5A058J40bU3!!nA^y0H^06j|-jwtipT*UJZ=TC;!x4B9Lo1 zDj+X#0x!l$9+m+AhLL*z2v`SmOz0`F`cmq0Jn;ZeTS`9#KOOiOW+Ax1GcKp!flmVt zDB_F}96fnzCPw0~SfPi2)u3u>axM>fUYuQ9|L?9lY#vkz?5=hp9-90<9=Ys#%~1v4wH@lX5c3np~L6E zd#*6}y}-;0+8cfXz#n2H4=uoPRkSzoG~ksO$$tQNH%9zy0bT<$@m}yXz)vwP;GYAp zt2KBXFg9RtH*gb1>Pz6+LFyO(Gl36cWc=I)jJe7#FR%mSK9xAd?rPc!xWKqorXIb( zKC7uC?A^dTjFeH}6cji}|C$C|^G(WvAAvu_NdLMW*ol#{h`iJYjFiy}T#MO^|E<7d zn62PyEn4NTC7csuorkQM#|U%Z2AS?*lz+pd6%J23o!p~L)!x2w=fd_2H-x7ghel;ddJ2E zKJZK9U*J2xGGnR0`|mYl<^#ZA{Tf=4*1f>ZzcF))z(W|RFM-LwHMqcCm{$B3Y^7Y7 z_rPxf&fEt7cmiz(*l#=I2zWAZHb&~S8u&a$^0{B|M`<(o*$?dVn2FyDy!CNTeX-vR z{1Zm{y9J#5gu%0b7N!nA0`J=a9~}Gv;Q2eD8+ab@SGy=L_`Sf>c2j=vEMQI>x7rku!F9D8!#o%ec zGK}~an0d&w!A)nZ<0X~Kidx0O@_)*|RpHd&#F9hzx$e8d9Fzz$z2zzv)s?#tM zR_^J@y`#@*O9JJdkKh93uFO`(B7t%bM(hRdwsE-&Blk_jUZC775&r^*es1gqiVVK^ z5h(W^1Q#fG8w3|9_YedZ_%j=qy9jcRK4*h{2a#nJvb@yloP3GDZuz`pea_8lj%S3(5)7nyGI3GBTmuut#BUii0J*caT% z*bRKgB%m^W!5Bk+obSTB7)#w<-|pWs#!(55d-VgjkL&tQeT{D_*>P`v7yrcVe5d`D zZ_4C+Z{picB|G1@{f%)UBK8WypnY7|*zw*ytyUb!2MICckL$7Q+4ac)q+(wG`ecWC0}kY)#sXAF z`>!r*?^jwuUl)InzsA#kK-cASz>uW;KR(n9w;u!Pu<09@JD_fnpa$+ zAG1FAdv-;!=*OD>Y~oDmW7gNdy>P7bv2I`E#>Uy+d`H@)FI9=hu9OqiQUg-qjymOP z`0RqLMdLappR=Ab9NVcZr{KP%Di`Ex$TgAcB6|qs+zr`+d^0)k)TtBRql`D#4jG~z zfBbQco00Lwix;b`tSq%@(QqrGDctWnV5;OC?(?f?2&5Ie($%fK8K0I-d z$Y!g|dd4en#89hBk<7f!L)qTz_~E}IT+4;4S96t?;wO}v<>4W2H9bUCb7asC)>WQO z9oA>ATgoT$C{XhWhUo^WMT-{7$Hxcn>1e0?{ry!?5Z)Uc7N&VOc<^8~Y}hdM&_fTY zM;>`Z&3del8Z%~$8aHm7ii?X=NlADgE$qk4nKM=T;ipPt`qu_l(5+p8(%| zG1i^AIClg1F-7nNq@H>f@GAhH1NdElKMeR&PVg-O9~cRLF#&$!V)%!-@CyOIr%0(o zfIkNKF9H8G;LifS5b#%=;C)+SehVty!{AyvcOlj~Sbr701tmOOPsy?NO1>DZUQ1N6I}L5FS91E$ zHF(Txk<|fzJK$>pzBb@te~RD?iREr3z1k}oIatZ#iAr8fQ?g~flB0*N!K*rWe@X+K zNooq8$p>oNMdd^Ci|~$TsrNAU-V&4yeo9H=3MFY9l&s&U&)eGd53fG;Y8e*kX>>5mp-(ZbVc;bpY27cG2+7K-YL`mw#J zOM^vSNfdQ8P1H~8Mg4L}%HZzgT>(!H+za^o0N)hwEdl=k;Cs~*HN3s3#KEE#B%-Y}QF-e{9Y1spzPxF$ zmL}($!NI+QdIyE*TLW5qw`lI^*|Kk0g`nQyVPPR5;lTj`K_S*Q-d~$##<97l1xSXKwQs%mp8ECs`|AdLG?h*99QcP2J}4Z|@2TIU zzXP`ct%(BQtpPz11H;2Z!>x_jKtuNi4gPZHop&}KKpgp;FaM7~FV;roDp<(|J`WC! z2n!F72#xS4R{_txTI=?EM}&ljMubH4xxdl9jxNxHwUu|90id7l2kR~j*Q`C=fda3< zKiz)&9uZ)1L}++~CPL$A_z(Q8A?*W+LU=@kwNalw_3PIM5oOP(;32SEpTQct`}e+{Z&x*`$v{JOa801$C%aw??}FYlJl-EHt7NOPG+- z6c*g6cd&1Dm)Zjz56G*q5SS~+b89zWw_3NmxYX+h42fbycmM?H+Vh~Uo!fP+Rn7J8 zFgy(I4O#BgDLDArbE~y?(4Zc5YS!q29)hiGJuKu}|JGp2-Jl+K-BvS@&w~RXuHgn8 z{3CxLV1akkt24+N91+k1vR3vO&rRy*R!t>rfOD}6IkhzZ8GkMXZB)!s znJ<^B0xI}(H}+GEKlk8+4{Cp8R&?Jo-{X~Oz0~~JP_-l}SZ$gUs&bdjQeF4Kr+}U7 z_lc-s@EzzgOhfs?3ooeU%a^N_D_5%Y^mMgm%^K}1Y}~j}`-5-1@rI(W@X@YU)N=S6 zx$qVC?%k_C{P08V8=N{>piZ7VsZO0brOux}ufF^4JN4rah1xf`eEG8a_19lj+Er2O z;VT^a#mUb4HpN8O6%!rwa`9+Pbki}>Ey6^%R@IYDs=e$~gJqvelp`ulK3D7IH0JMX z^NjMvgc#`#cucm79{_w8zy|_89PlFmp9uJ;0lyOP8vy?v;0wy;ng9AJVBdfJl>d`{ zN+VU88Z~MJCBi;tL;h{#-on?{w>3Xm8Z~ln)U>sSTb(-h!yj(w>D{7*R}0^IZgpGT zh3iI5n|XPmZap^-Umsr|)!4JOw{Mf$zV%R{&Ruui-?(WDZ{Is=d*AQ4VX=6(_H}i= z(;G0Y?yhrJBliZaeeZB}tzD}|jXPV_t=p*j?TuPDxx=+KZ}_@-+*{M7rYGw9`ZlRm zgYEyt{kHnJx}#a`TD5$z4rtoqzG{u}6d+A-jsATa-{aNH$Jf`#3;3h|);>PXeSDhw zX!;r>S&*7G)t4%zF81PUq9S}{on25?mU!RPVST_U55xvhz&%%wBD*LH{{E?S8=&E_ z>#r}sYu9BBlV~; zIF671kwpHmU94`Zl*n5*WQxCK)v8s0!@RS-u(0r(@4x^4Tg*KtFI>2A8fC$yOP30< zEcrQN%Cr}XaKyCd4+I5kFYfLsrmxNux+J2F3$$9(n|t}A=x^*VpzRwu{ey2>**0FA98_v}Vnkbp{U?o;!C=u%}zb=luM9`SjCIHJ%tBjXTHY#EBE~ z*=L{WYtm#gd>;K7GI!~RAATr?-2H+!&;0!J&+_AsKVJOkqmN$y`s=R?(AQ6d0iFMX zzI6r;3kmy2@rOSp=&LLff0M~qlQ||P6MyoGrTNTjW@#V3A&%4u=&&x2962J))D4aYOX>%8hcNHI z|GuVyV+j2hjsy1UxrJMnaQzGJm+(1sxC3aYs{S^-a^;F(8q)Ib=jYdwa?H#zz`mJm z-@aWi<^rEt>oCWFV}gA(or(LtefxyEa_rbK{h2h-22kFpCmbW6ZFh-0xL+jew8-TvSB^kesQ*<-8vmU;ccwLO-n=t>_=T{Sg7MHa(B^Oq z$XC+Cu^{gJ%<=#7%P)22XY!o68GVwRrjD;z0MNg;)l$XDKDbn{Cz7z5h z_)i)z23_74=>QtyKS8{s1pD2GMB44tVuhW>Dy4?lC#5Ve=-9ENCuCtB>A*N>dJG*b z$xF%+`Cl0w~lrzdbb;Fd@3#K7oi3|h{ z;gJ76;5TXTKPb}egHjsWK^L%3F5Y>%I_+pxlExplI1PLJoiPpzsb{n;mC-?YcODZX zS1ieYKIgnZSlSuqH0%^~lr(%H5(XMVK|}5Z=Ni}j`~#jWyACl8fBNYs!8}tglLnIw z9hHrVp~abwUw-*T4!yooUY-#y%Mt_Rg^7V0v4_7A8Tz%z;1ePdq~TMCK0{`D8hxfs zf z4s4QFruLM~$^PfHiX+;{qf6MD4gJ7qSKCBFX*n2Ji z(6xp1hp2Og4nqsafb)U#m>61E5`Wss&9j3f=ZPMY1sYxk4e66g@lP%kdGtJJI3w~m z&_I2rO$vuiGWtv!j6RbFqtCQS-rF_)I7w74HKd+#eu1A=mPv!j73na#;!FoWlLn@( zDcxkljP8>2cn^7X8fci}FPDqX$tO@}(qIJ*h_T7vob;JCiTWG_U7$_!gH7W6Y;2NO zo=CG&{43fejX(VR1)V#0_Jofzk95#3vZTzA4*EPSNel0Bt~GucpK-pW&%pFXYB$+3 ztDCF`4cVY!9cb9GbfR1;gz!`$odun77!yCv&!EBh7+yO|fy;3p_Mi5`$ba|l-CJ@j zOs2jPZ{kMW4K1|&wD(-s&~9?B;@rlxbB>?94jMMk>Mpr6dWan~RMh8x!zQK01<8W( zy=8uEu*@A3EGdtL$a9k)mM=d!D5SyJ$I$u=o5WNZ{;>C2{(;Xz;!eC+5+~wKeITFB zn9#;M`^WT$NF(L{t@*v=P0+9nG;Ep)8lVf*XVO4@rcGK3yGj}slZJ7<<>|4YAtpp- zJr=5IAfEIwI6oU7qci3=q~FOuZ3gFH`Vq|Q)~yqp%_j6qO*Z4f@ zU0|vVS#uA26?Nh3{}tC7|2A#fbivV{c>GlRdHB(K95OO8WYC~Ng0n^PkAM6_5L1%p zpMPHC!}UG+O&T~CaGs!CF>?(=8fZ@`hnx$^qrK0C$l+Ir{}tK4X38}m1G+#TgZfOH zv}{@g(ZA{X3wwXhAQU>A@&j2MKZ!eW zpugmtNrTCT4wh_>nKEVCrfvOT>nBN*>0??2!yrOcZ*?;_49$(%WJE zE43_<2I>X(eTWb&K*3SxU!wv7^*eM8svrj2U_yNCWLE_LgP%@ZtJC z$AC1LOd8C(mupJ;*pz$X$&xZe+KhbhK7A_s+^{A8#NJaEoHJa+HN>spPq}BNEOEb? zG!ZxMIpge|*5BaZU!cM6OEG_7ik3KnTDSJe)^;e)G*YH4Wqs_YI*Rnue&TC>bzdfR-)9xJLj!oq=t81assJ;Jyd3w51vX1KPdg{#W-?)DXK0I$ z*X#c%?xa!UZ~TAodmd>pcG1vcXkbZx(>7u5*6Rey6z5uJ{t{PS6Mv44@gW%3q1;oJ z$aCrtY{nAcaVxl&;qNT}v=PqZQQ4S~F7C09963^OE?3L9;kk3kdXy!~I`4B1AnqnU zf;H00KY_c(pM9A1FXo^9ux9*%a$#&Y}qm`&*Znsq?@us z-J##aYsw7U<6Hon`3hdaaI1VL?o4|B!FgUJ{w9+KlW#O8qzPxD^?XGcBMfOHzLc#z z*iO=7aEE`o_7>&66zgk$_5Kg^ORs-1f6pT=H*|&4Z8ocGUH4^L-Nz?f5J|b?f;Ml&YkpMX#Xe&oR2tnlE++glJ^`3 z`T}Mgcukv6TT45JHHD6Afad=+?xaJ@zq4#qlyh@!^wzngtn-?6I2M$7@|iSJ)*(l~ z!ACfQvEsbSGZuejZX$j+OLwCJ&mjE2%9 z2co%36Z>+2u$UTqdV%`-@_cDTwv-`?xg5#=T(1 z6gnWbGZK5lAOEOPx)BbfwQ-FaHM(MLmk6CMragntc^UThEarmmV3&@=KhMBE**N&X zA*hcxu_#aY8--&K<6xYOd!d2Yzh%su@#3QwMe?yLhwmdXeUJLrOHE+IGtp-;?I&#{ z*Gt5K*~Bm$KL2m9s~2H&kHBue!G;+#WxSDbF2+~5C(iiLN0&qng7zxJdOc{Tv9Az? zy{BQsfxZ*ho}3?P*Etu_R@0ZIpTcMS%rpYAD#kn+Yh#Ru=NA~GVtj{jf5zCDu17rX zdvFbaHE2B63*$Kda$e&)m;KU@CQlsnYu~A~#nQiwmpzQVTgLksE8A4${It@~3}QLU zgYKW}LHY>H#DSUiotZr0{B_~&52M&yT zGJdY*5jZf`#uyLfkufU9IvFQ?2s(na&oL$*oX4^65|8iSjpN+RY;d5@L7vdJ&Y2ag zV||Rza37J0eKRxm%J?y3e$Mj9vn-6!FxJNy6Xnt8O$~a*^iMy?#1}cQ(oZw~o56(; z+*jsaU?%o68S}+=>0~x^%ozvDn5G=fVCFPl>|5!Z2q%*f-^z zB@^RqjFB*2$T-!O7ZYw8Gd%aRNKye}p1^_Ud8iYN*)kdW=~qmjK0Q7qC1o6aP-cS% z_f5zPCho5@*2EYGV`YppF}}e#8DmV0Z7@d0_|lBgrTK+9u|gcQJRQm!togkM&_!S|^M=`hyQh zW#doZ3~`7keD87?Z2{N&^v_8*aUl;_9?p!_aYM$d7`tW6kg?}gj(8z;g7Fc?3R4lI zGCW{s&NiB{Tck4ir*7f9z45UBow!`s2idJm9YVv9y6x*kq!S&kn^YDoLrN&a%||;t5-+t_f97r zh+|G1HEPtm`2MzxA3t921LKUO-n%esAM%|1Apg0(qb!gg#J^%Hz{-s^QB=X%Cv7+Zp$B{=u3={D;x;=xRQ5RZyuL;N^z(ROfMisri@)4#h>^57a2 z{>M4S5*e4k_e_QRuf!oSF;VlK_JH#s+cq-5zGxSWu40}jL0o1GWH}i=65cYSc;@M5 zYbp=&3cO!DcI?=97~|m{J-+ZS91F(RFfZ$V=ns(Z?4OxF8GSTUVy^lb{Com!twOxw z0{Z4s;ATn7A9avz(YGVNxtB{B zcDYulO49b1_6O(a$FaQv?8$S^r_Et(0q-o(F=pxo@na$%%pNcOWyVzKw}XZi=(MVR z6F=R*k!SLinRqa>Kh8&ZM}oEuJgZ9DDRUez@|twhCS&hq?H}x0_s@P{Yqb5Z3=iW2 z<2wg}?>p+fV)}*LbD}){iN1CJq}R;9lqJ&3HkoPjsB_e9(n%TP`5m6U!1n^QeYi!s z**B91>95FlXZ~{xm}z@y`#8>cCj{m10`|k6K^xpZxz)t)nz-F!rheVbzFilu5)XW5 z*QMk~Xo_HyrI)zj6J@^()s3T&uLhT4^cpVyu;Ga^g<;XTPt`3e!H$ zMXbS=1826uwK&&a+>7A4kLyl9tUI|!O`nQ*({3?w4Z}6m#(yUY+i*_jVPd(b!+iv< z*~mYR6XziMK}_493f2A=*B@MaaP321m+KAtif4pva2?(ccyRpi?in5DrVS$>PV7yW zEvf!`JxSl4emmCsoxzTT)U|^cfMx)i{=v7sG#D8GjD$&eeYZ zOsstziNtOu|1d9TyTzCs&kqpR$lUr_z2w}9BbuLFLp>R*`@dx5hq6aoPrJjh#CO*< zPid<;mS674kPUPC>hs(yr}dZpZ@j|pHye0-cSZYZv|p4P+HLw=91q%4XI%K1bGd9wR@ZM}!@DfqO0W3-wcGHFbzJq^*Q()J z=@s9-Rvm9N;*~|ed98+{CazHDc1KN%e(PFIyjzX#-Y_*pS@Aa%?_n8&x5o@p192UO zzkTqT>CNhe@C{w`KN=){Vi~}PNY(KVXq8Jb@FHE%-X#25R;-FwW6)YGeo-qLEyt@E zH4(LY>pJa}AGS-oA$P)iXn?#5hdbh;f>9?9Z+D48{pr9a3Rls(k0EG@PuQ9T@2`nc zlTl|h-W?Z>-YjaUO4grP`S18@t4mqmA-JE6n#3sqxW%H6_$sv-iudD019CE;qJSs+ zX6k@n`nuNsFx_vmQ@ic)rgi3ax+K53IqV7;@?ny$ACDF%I8itW%YaU(AFcbud$CnB z)E|KBF}fx>lK`HOiZP&i659OzJqw)aV0^LCf>EeCzx*_AgB)#hAD>YQqQF5#L4I-`mxBQ*eUq6)G^V?We=SnhfV`1f1h|j^pxlcmI?gp?-`XG z7C&X;_~;~0%jDRg(WCJ*y8fOqQ4^A*J$v=^Eo-|xa9R6KHGbE7Pv3I5_Vg_y8sI&B z4L^HD21N#igoF+3JA61kaHRO9>|+@x@cT|h8LpXbnUR^pGnE_OF^&8CRv%k^W_9su z*L3%E?{vTPe(A&0$EHt9pP#-YeO>yt^nK~a($Az9r@LmjXYiLBjsixlc3YkL>f)>= zS*x?wW#wjV%i5K-FY92|v8)qWXR?a2inEl>)#he%w^?l7wstl@TcE9D) zo!u_mFFP>1U-q`_W7);o?m2!r({dK)EXi4&vo0q$XIBnriKLd}RVNwKGEy_t?Wre9`1&BsSG$7UvEPRmTqBxC-Y z{>y>?T^wlEG`Rc7$mx^DPK+Pfv2E9p3HoE(=xNcl@2VZyzgqQsG``=u%p5u?B51>Y zTrqRKX|={h#m3`JXbIDsS=zL2W5F-0<43!@TP9D6Y2(K`wPWKFCMHd?Bt@G~$CdY>OuEH_ zrrJg&jhQl{-6Ix%bkR0>@+j{lFYh+VqmyD2CXEDIQ)i4MC9MLQdp%(BLUw{_Rn|!K ywAe}B?9sbbE0FAR+dcZV8+e~|B}8j(;7_V{t_(`(E7I}9EOf-Wp~6L7^M3%MI4z|B literal 0 HcmV?d00001 diff --git a/libs/common/bin/easy_install-3.7.exe b/libs/common/bin/easy_install-3.7.exe index b32abd2052ed131cf410249330bd5e7692103539..f5a3bb997024f5ee812d59102ed6ebeb28368f4c 100644 GIT binary patch delta 29 icmaEHj_t)cwuUW?>dTm)G1gAkTE_Sr%+OfQ=l}rACJa*m delta 29 icmaEHj_t)cwuUW?>dTlTH`YwoTE_Sr%+OfQ=l}rC91NEL diff --git a/libs/common/bin/easy_install.exe b/libs/common/bin/easy_install.exe index b32abd2052ed131cf410249330bd5e7692103539..f5a3bb997024f5ee812d59102ed6ebeb28368f4c 100644 GIT binary patch delta 29 icmaEHj_t)cwuUW?>dTm)G1gAkTE_Sr%+OfQ=l}rACJa*m delta 29 icmaEHj_t)cwuUW?>dTlTH`YwoTE_Sr%+OfQ=l}rC91NEL diff --git a/libs/common/bin/guessit.exe b/libs/common/bin/guessit.exe new file mode 100644 index 0000000000000000000000000000000000000000..cf84d1fe1170f676fc9656ad1eade4a563e40884 GIT binary patch literal 108377 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 KSvS_w75)cPWHI~z literal 0 HcmV?d00001 diff --git a/libs/common/bin/mid3iconv.exe b/libs/common/bin/mid3iconv.exe new file mode 100644 index 0000000000000000000000000000000000000000..48cdf01e2eceb14398ffa15fa55c642753ac0b02 GIT binary patch literal 108399 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 OYN2D+4Hc^C3jYIjqcdj! literal 0 HcmV?d00001 diff --git a/libs/common/bin/mid3v2.exe b/libs/common/bin/mid3v2.exe new file mode 100644 index 0000000000000000000000000000000000000000..5c4ef10859c5495e6cf882ca8bb6f41aad1985cb GIT binary patch literal 108396 zcmeFadw5jU)%ZWjWXKQ_P7p@IO-Bic#!G0tBo5RJ%;*`JC{}2xf}+8Qib}(bU_}i* zNt@v~ed)#4zP;$%+PC)dzP-K@u*HN(5-vi(8(ykWyqs}B0W}HN^ZTrQW|Da6`@GNh z?;nrOIeVXdS$plZ*IsMwwRUQ*Tjz4ST&_I+w{4fJg{Suk zDk#k~{i~yk?|JX1Bd28lkG=4tDesa#KJ3?1I@I&=Dc@7ibyGgz`N6)QPkD>ydq35t zw5a^YGUb1mdHz5>zj9mcQfc#FjbLurNVL)nYxs88p%GSZYD=wU2mVCNzLw{@99Q)S$;kf8bu9yca(9kvVm9ml^vrR!I-q`G>GNZ^tcvmFj1Tw`fDZD% z5W|pvewS(+{hSy`MGklppb3cC_!< z@h|$MW%{fb(kD6pOP~L^oj#w3zJ~Vs2kG-#R!FALiJ3n2#KKaqo`{tee@!>``%TYZ zAvWDSs+)%@UX7YtqsdvvwN2d-bF206snTti-qaeKWO__hZf7u%6VXC1N9?vp8HGbt z$J5=q87r;S&34^f$e4|1{5Q7m80e=&PpmHW&kxQE&JTVy_%+?!PrubsGZjsG&H_mA zQ+};HYAVAOZ$}fiR9ee5mn&%QXlmtKAw{$wwpraLZCf`f17340_E;ehEotl68O}?z z_Fyo%={Uuj?4YI}4_CCBFIkf)7FE?&m*#BB1OGwurHJ`#$n3Cu6PQBtS>5cm-c_yd zm7$&vBt6p082K;-_NUj{k+KuI`&jBbOy5(mhdgt;_4`wte(4luajXgG4i5JF>$9DH zLuPx#d`UNVTE7`D<#$S>tLTmKF}kZpFmlFe?$sV{v-Y20jP$OX&jnkAUs(V7XVtyb zD?14U)*?`&hGB*eDs)t|y2JbRvVO)oJ=15@?4VCZW>wIq(@~Mrk@WIydI@Ul!>+o3 z=M=Kzo*MI=be*)8{ISB{9>(!J__N-a=8R&n#W%-gTYRcuDCpB^^s3~-GP@@5&-(G& zdQS_V>w;D8SV2wM8)U9HoOaik`_z>Ep^Rpe3rnjb<}(rV`tpdmg4g@>h`BF#WAKLH zqTs?sEDwi<=6_WPwY&oS9!h@ge4(br)-Q{|OY*#YAspuHyx;~|kASS3FIH@oGSl?L zvQoe8yKukD)zqprHiFKlW%;G=hwx4l;FI%8m&(#zU|j&_bW@ThNpr9D0V}xa)%aIb zI$i2CA2mPU{0nJmK0dxe)dY-`z>ln($ z;r!UXuLDDi42|Zd3Erx&m8GqlFWbIX0V<*Gn6lVNq%gD>gw}da}r}ZQB~ns?p8uy4i0%1Ti$Vt|~OUth4=+yEmPu8{3(w zUDkd@?w?`_J9HBkx&ZF8v{+9phcT@3J8VI~wN7Ez)oJS6^dhb2N;;{RTXB`K*E$64 z3rDqRtY&&*}9yq2oUcvD7K)=@bWqC1X%l0jk)W<5-WBYC(#rn4H5)gp#eHMmwlLJq=^%|*gMQ*pq4VV(QhHA4CGj<;!d8i*#Z8CaN#*>VcCnj~;kkeUa{LUoKxFCaoQ) z(Lz++&x3Lwz;=6UnhwM!MvN17>{Qmb?dwgsTmzkLB~jD#wiGz73hc0bFE|C9KA#|= zH}%FQ>c&Y5z*TJD-<$$Y*WZx>5NNe-E-TfAt1!)%Wc@I;ZuNwxDGGasDIMyUNiVvG zq;Q70PYHcLO=Xgv2698@cJrkun-^>P2}|fMHlm7xaZmE<{&cQtb`{N9zj0bRmpW^T zzQV7oTs0ENHe&mxQ6DI7qd0SU4;3o*2qRd`X1>(=ew})X5Dx zx$lyzZM^emtdsbk^u+xwdSX$lp7h*2CkHCqDohShL)V4hM9k+UQLP(GN-H7!C8gyq zex`xuPQ(!g4}S>0r+CyH+xIAMP9Z&+?BT1!*kA<}dqRn*FwJPGe}l-sw(lGYN1b8} zWQQjQN`9tdtF?#aqMN?wu4E3)qGxzOhwr*vb;kX_%&U*-=KLr0raiGc^x8|=Wqt`N z?L0luR(~BF;DS@~yKDN7|*TJkj*-B%s1{65$`jY_(C#P&^rVi0?Ro4iaFbR)Z2NLxS0 zTL;%Kt22(A8JiL`U$i!iR&zLxx^E%H=*c-=+h@sisygu-_#m4J4LQqB?~vXvP4@yQo0-^oki(PiH+=FZl}&W)S-qI zk>W;2Zl-vl6rbe4X6feZb)l-Mv2oh^5t8q5@(Y-SPoUZ;N<5Tdl!h|=x!1}5)E;}=RcAXJ8(<$^13IV==^rU>wwq$hX3V4iuA0>h< zuxK^)myr=p7a)oeZ+g4u^9(OmpFl8J@{{UJfy=DjAf8lTTD00iSF3Kb9|GdM-PQp)0<* zZkW*V-TPpIXEKDks>&FQ?qoV&Tfa*;TJyB^yJa8xcch+*-cYj6E7HdBX!5)TIXSNM z4C2L57KVd0rioelfI{ELMrb&Y}?h%mk5iSTXrmJ zwlk6qsS{}3<}Uc!G}Wr;Tek1Tym8$SrWokvCzU(FVIAWTEa1pwE zBJ6JdS@$4RFBV*~g^Eo9MAFafx2rt|uRsR%xpNVyj8!g>2u0v=>eO zS~4nHBgR%cVxB-_OwP@%JN(CpY3qHvqsbt-TUGivY2Dr$b+=`6PJSkbWF)!Jn=iZJ zMt}mOG~-m{)L*SV+yRH!c@XR%)K^BqVRh zq&wib)2#d0V3BD*|F5o2J6$vbdJGh`O-30SrMI;e*Y&m8c0Bi^cD-$Daq1haK*i4o zS^0dLE!U;Du-W5i&*6##L30bjy7q7@lQPyCc8<%{>0)|vQlrFG_D_+v^1uh+p+bhA?!)dFEqi$(hoT?=hJt20DQXmOiJ``9LY)@=HE zO1esvSjV70vmITir9t{Om5D&<%?UTa#`5Sp-x@^?6JCK@(Y_-+ye_agHcB_zSUEYe zay}#@o~N5_?G>%q2t<~g3s!Y+G*Mj=P3Zn>mA2=HCm`lzap|)*f|(31R{)36WvAyz zfea$wK&B|2YxO{n>twI{fk3f0YVK4T;XDy#cUe=*$V6#=30zz**pkdJOUUdHcyGKx z={=%tU83}-sM&@LFz=EaBy8m5*VS4ZYhB<>lI{BnIk4cD&H_E|%!spiL(( z$1W0V$;KX^P(?<}XYHqoplpQo7H>!m)d{bdPaLde+h7(tf+ZB(6MxWZnoX6&>|)(q z*DB~wjMmL&u~F-ZIbJ>BJ5ZM6ik)gUbdlBM`Quqove#M~lf*ebB4nBg}NN8q8e!? zVj>HOMJZ@LQzOdvHUSih8gCt%IxvyHLmO^Ea(*!Nd-Zuw>`f87{SkAwbrcIp6hiff zt7^x@FVoBVwDl9eTxT2$))(-5-O9W=qunp;*yvYT{VJ=~FI-x;pN&=5ArA%W0()Z} z=?f87g#Y@j2_ct@T|gzY^?R)mq?NdksZ}7gJW^{18>hCuy{s)%iDWGzC?-DRKLl?l zlnO5zQf3*!v6nJ;)xm`Sjm!6zf=o%-07p#e5?cL}gBtB`Nq!dTtt@<7#(o8m8xm*XOvN65AL(=C_D} zJM9UyYteSSwriu8{DkKl6tSk&09e8kMrjh@N|SS;@9l|6^W@_Q=i{`@$NUzI6|VF> zN{Rev95oVSa&%)ew#+uKZf{3cFg?f64ASokLt$^COgO2#BW71L>H7~o2Zg;=Z|nCM zZ=N18^ET^uY+VpF$K*teqc&2xaTF!LhIKrwGne_WBX+B_9vi@rt2GKHy|kQxSUJ18@{fEswY{>va~$3%JGyYfr29k%@bck16c zdf9Hh?|r@PC`@3R-j=#7868z@m3)O|u0`Iw|bd&(6~U$UMGD@Vncn>Lm}{NqU9US&{gYu`~lU+m1n zi1g$#vC1#v|9B;ObTzhRor!#90$^5b(Gy`buihHrRfjV>-l^6#?Dg3lZ}@PRD|I(> zVcp1Kiyr8xABHMWk$xp&hFzvUhIKbDi1339ve8Ac5ON73NDM}^^I8O?+8zk+GVA0S zG|7G=o9JQQO;-x!z=zz5c@^<{-AWi)tG`b65v40t#CwnzKA}>?+z|q4`eNlNfRXZK%L4$WHQ)8Sgo0 zwE~@9)+4fUIf8fW?9TihJ6Hgttrta)MqB{FTBqxu|CDLzEKWn{Cn*>&wx$DtvzSvC z(4Jr-g8~qe!NL-;BVhBlx}Y;!It5;VT~^q_HdZcH!a^(MA3%zpy!zmpD(NfkvF=9= z6p^lmDSFnrRVn4npverH%%I5(CT}SgTNGB)0sCY%@`7%@lG#4Gt*2;3c3;0E8(QyS zoo-l-h2)DEIh-3t!@^Gefe~>Aq|Sbf{goW=Op7FDAB-5amdpAhatG_BQh1V>p|DF2 zoM~XblmiX(kl0U_veatKBQ+uz9@Z1{N|y`0j<11Sd^JtI@w2S`$mW?%;MWLc4%=HL zi!p2d7Nf9k{=Kw;xt19k$vh+UMEX9C2D?jRP0wn3ihvj zIKqjR_QyB+t|%#l=^@PkY$HlM{<4z$Jve9n{#ZUhYv#%_q#uJnen z7S7e0{d|oCJ_u>EJ_(yUqk*m3cisoGsENRi9?F=l*A~&-*(<$4vm*-sUaFT_dJdnX zrOQM7ERMPl>SbN2|4`NV9yZ$|0jqv#7_|5qM&SK>FdA$Qn}>sahte?IEg|!hNZ-Lw z+2M47yawJ6YgZhmd7`)o7cpN%77HvCf^&@h2FBhy;L2rI>K+Cp6&?pq zlFhyiSR(126>L@rL1c*79q1?uBeI5<%2ZP3K!*8bJ8n5Vkdy&9Re{a#rI- z6fv$Y@#|&(1pg>!eIKW$IeEqD_akO!YCNey`?q5Uh$a^MgG!T#n1>V}I*O@Oh-I-5 z%k{Du%Iw6?)MXzjh?<)@`1%M|Z2fN100q^u)YBKp;(8NX!a7BpNWL}bB60|{!@3IM z&!_-j!}^5^fVs3)8n2d}7M6&L95t6HGcO7O>k8tJiY2gy{mtC0V*s z;mM4hWAvYlP0?$+)i!p-gT`AH%yAiSovz=pXFBCU*-y1#y_wmwf!PgMrEDEyp_Y+h-3$ZW$Ny$8H)g+M&odOm3D+qCuDCyTVF4s8_v zmEyLRLz)cEXCoqszT`H8*!|T3k)9}efv(zxR?xmMPtJ#z>B&Eo77PE!jE`0XJbxM^ zJEbz?Lu5g--#l!-Y#gzXP3G6p>XOps?99>9SjC=T%MY0{>#J9bVPGK(CmAlr@LDVu zdtE8Cwy$lsu#8`O8L={lK%5}c`pb6GjOmh$5gX((WMNF8jU#kU?6HQLb+0+w?hE$3nE@wxIvFA6~zB7QMVyoEeHQuBH-S!>tRw89F zyIi51ALX;4mfyl>Gbw7NUa`Y^`9s-NepV{j;n;E-$Ceyj?qimR?nQpJ7Zt@YCfL5$ zX%(74|FeDDa8Ol;N-078H81eqW|LX(_9$cc`%a*!#=7{V2=)|lNG5a40)v6g4t z01XUUv68UZ2|@vkl?ceW7{YVw!nCy? z+sAnJ?mvd`Ab`J#GpRgV_N#doE}<~&Z?VHb%c3L;ua)NW2qzfhmeh>}dH zGKiE|U&0iVSyyQ$NO;+GkhAqI3{1v-UXl6k&ogShm<+H}bDWf8ZLbv`!7=F`^V*WW z%|fH`g0dA}vmj?dt{;}&QQW)P9h)H{A4EQ&PP7V>>J53l4KOcs^mIW( zWkEdG-lC&N1l;w9;87FIEh#42)wpNXA?u;BStwK2f%x9dIa=c%`6v*^^D7Rdeo3P2 zK9dB;uN>7oyTltCA%$60W`E3W-dBpg zuqcq@x{}^i&v~(2yR)n>8M=s-@@eAy%xR>v4&Y%h*z7^|kj=+ut-*SgnXpUQ2Za%i zw_32)!m77h`9S6v$7W)#c5Gu%xh%>rSYMFAD@|Kh-5MzR0ebF=8}-^F_#pg>cMe^Q z_fFTrqJD?X&Jg+pQE^7T9S;~YZ`N{LIq@lM=%?CSV`D_iRT3c{J=yaikxU5%rHT=TI9ln9_p;9*QY6sX)@dJei;QU6QC|w1dx9PPU z-k*1jcMjN$eZXl0=c@we30H5Z#G4Zf18#{O`?4|fubhbI#LpT6?u0J@S5*J&gl|g| zx>4w6bp!F}L5Qb)5yTF=Q~b_2auNe$u2af-1--x-Y8ugJ)$~A7xqyDQUb~z9yjp?2 zS$2CCh3xpcnb+1EDhBdlycVY?TH-GQhOBi1Em;xS%mih!zz5d%5ZTK)kgI(;YVM1) z9Y?6R=*3Ee3NQqA=9m}0tBfPY>WV^F{KDkb!>u=FvBx{<@$4HF#Ty?(D_|c16@7ar z?3sMj4pkIxD3B@pYY^(UW7-_E@LkG|E4F$T>^}02mQUF3kyHzn_+N+p{xB`ffEMeA9vW5-D%{ zZltI*4Xan_uaQoJoSn85x~zjwdZGe`c|L&8DFe`!Uzz7`w0>!xulJ>+=37i-p5mR> zWl?vJ+1b|P3AuYhVyI7#LAPEYZ87i$tRpmE}@el^F1lN0erixJ1-N#3v0fp0!puf z11^VLsS9qh<=8A zl(KovC21r`^>K0LV;-uDR<&qv-K@mIx|7<^+mo|TDsK^_F=k^064`x9BFi|CeU^vI zA`v->wGlB>5s}S`2Vld*+LS4GWdW#Z9=Ld+EhF-ng5iU)X7A68`i# zO|AEyO~DJK*d*(2vK_TGJ;J(KCFF$1nt-h(v%kz8V%#2jMxD`gWt|!-@k5${77Q@!{4z;ze=7&BScC z{l96Ke7GeU{#P5P(1-)>pb!x>_limI(??L33;=E&UU`S^Xg(o6V~Xzp2+b869oyFB~+oK91m(zDG}-Ce|yro;clXhx0fm zqA!a1;w8|CgOIS{tHtHPM)Qnv&@IQrVjZ>Cz6}8;hEX6s#`+#jXAT>_&8rE)U3h@u(3Rj2wHPF8HLr_+u|u2h!@v|soMqnSEk8Zd`9UErc zRN_h>v@U-yBXM8Ej^Rk$+sR6^P!=M|4(TT&#@8NU-8`?Hjo1~wjxi#DFXslCbHj#H zR5!NB>1Vtka3nsdw|a3-Y^?Qbif>?ajCQZ}h|~?V$4;Z2hvePt!VjWV5kP_Mdzd#2 z(Ya9OE~}OG95vq%MZN6^iVy-|(zl&p4c#oK!g~#g9ul0wCtz5||XBmlcb|@y+~5^oMA2 z%2&t|Z30b#v!su;P0>oP@n%l!68gTFk*t&4-cTiC(g?CTh0XM*M_NA`XrI~P!(S-N zL`<-L&IbV?K2X3qpYwnLW)JqoQsvmwRaiiIOAWlUuFCW7CR}XuDqc-j>a`x<)1Wa~ zw1+(1-L|GuLWkn}HjH3W>Zkjq4e-!WA;hn0iSIXW`S*t~{JgUpYShtg%LoE=slzv~<=K*WA*ElMAxu<+e5ER>PXppG$|uZeA(Temu%&q(p;3AFN2!kq zm=?vfxfpqDEN!LF)Xm0H1wg{HMEXo-l13}ryyuWqH$7J>Xgp69ORBMSo%EOR{GE@T zp6`=69Ftb3=ONylwdwgfFVgK&D$mcnFSmVb{~?FB$0_H`z~O7eOlSLUCm#&_o;kIB z^GO&pU!)Lg-zm3^a<;FL4;!T`wb1X9I%}R0*ioufT+j91NaBu?NMeOwVtj_4-Bj0@ z_j+s0>1Gh!;oi!cvc4Mg&8Yc4=Cmj3w59_z5~=-$9!bpUA~dL*qwByWnz05DbT{~4 z*jZ@K?vDlzYTtT-qUP-5@^1W$cjLZ1m)7`wc?;yk#>sw)Ni$-;5OH_f-AMb*3BElL zTXVmwcEz1Nab&8Q-#V9uW2Z6VdwH||2KhpVBR4w8!{_^EvduYpj=@m1wadC|nCyj2 zt$A%;w3fp&nPJJ87ID86l?_lyq<-5M`#ZFGH^n*bFxrb{B4*!>glHD=IX zaR4E?rmXV`e=Jb3r)umy9O_=}HG_<;wLag>;c-u)&Cx(xabWC&VP!^jmFM&Ib z$EM)|j1Ueju0pu}b54-q=pis$~y&T*+xHtN5ij^Dv z^%7mNlKsbrMJuxz??mDQn__!^I>*gYDhiq>gCh>6y-yP!!np!os_nT!v)geY)f(H$ zMdxVz82saUVjQ{l!Fyx32g`P8jl0P*QX^tlU_Sb?kt&IuWuyvXIfW6 zvj(<2h5p+D2H`EwSwH=TECv*ISR}=U4K0jI?@X;}rSnDnja37_hg1U|)xdV^hSx;N zR_l)tW>JcPb8F@5C~uO{c@SQX_Wc-vx12+X_zdyQjX9DVg;djzhq7W0o z))<;YTY1Kqwi$lJ9G%8d#&=Y2g-5J9EDiLvQu;DVkGayNG;o{qwO{JmzR6Uh$UG@x zPCO=Jtf)bg*6_lp#3+w^Tg=a7c|p*fGtm(jE${gPmO7HD77SR?ytQ3_Bxr`(@-qAT zWfSOxaSdnVed(w}=&i-FC`!Pi=?<=yrTgx#ws#DU@R`1IyXR+k0R7~IY6mXQnIYJ=|Dqf4+{O?83Q*D35 zm~q?{FH`;v)-R{BFDCMi3*t-k>{7fQ)8nw?9TyWqG3`Ursw{KR7s%pMMe3iM)dT*M`1?|}%AZgc@ zX30+IPfbP!7X!AEjBUyvWF0|-nESBQh0Mtj(=rdU9mNVG#;RgmWP&-P(zBuAracc- zp+(j}^q7=iuyEi?+-C&NiI3TU^)U0@n#|Xx-UoNc*6NmU3HqR;Wl%dL zkIaY`kZ}eU*h+@_w{SA-$LNPRs?I`9&yRXRk~$gghBqUHqL4xmtMtVD2F!n`DBU&Y zA@L!Y3w6XoW)F{rN=O!R5%FX>|1Ypcy+BCeYqX6PttY}QV(d8A+D=AhCvAj2I9Ci+ zE_xz1LN~*Y8IN@_s1s-}DbcJjI5vpO#CDDjrv=T!AxN@1Y#t5bfti^9CyoyfXpL_T z2V8Sei{e7KzA*ct9Fu(Nld9;CL z?d=gOO0=h4Y+4Jb!Gh3(cScOi?2L8L!@ zXRz-XiI$JM!z1>gk%aITI}Ha2`#~+lD$VpAZrrCeDp|VeRi;hXLX+MU&wulyCi{V@ zp~_QZXJ}92zB_-Nbp#$k+W_m_M`OPZC+5?&W-o>zKXw6;Mw zPZVMo6>O;(y{(rJ))j>Jj--v{g0^&C9d>R#xu`p+I!;{+20Fvd@~tlHPH#Z}#D#80 zwJKsBYO=M&SD3rt(@+KWTkw{8Sk2`v+CyWht11NA9@xI&HVQx{ji8>XzDsLtBV)te zncQFSH2RmvZZP^+XpO58RW`&kpI(%5tDHnrJ71E)Kc>S>es<7(F(N@%94gfc zt}u%Qr8lQ*gBzd@RpP2l;SukoBN6k<1H@t7b$bS(TH|}1=7p2j`DH3Rgr=l(6PIL> zoLb8o5hMoHL6p-P+JoNWY5<8%Jy_)&dQZbMH@;n1k5gZVSDG59CRwN@mS3YieR+R+ zBAkSWPvs4(spUN{Y+l|!Sg;6&bFUYtQyI6H=HmrUtM0Jb+GO9GuVy+uB51tb7Yv*T zYFD3tL}TJ3oc#GNW=rR=aO>o4-~yYIy{l>KgSZEC^?)4Dv_{}AeTN7(PtHQSsCppR z-O&ueZ%;ojbgn0xqy?c1=D}`fMTVQ+(Hf7#GMidk%E4&NTj|ys)55Ur?JSdKcj|Q# z@lkkIq~gI09sUQhXE1Oi`1G%+0*FVX$zZ^K;H)*Biv-5nT~_VsJQLwR!63B8U?hW)?=-Hdlqq`a)%WG*cKqMfqu&U6`6B@bTa*hHb`MGTvKIJRjs3NL+*6oUu`f zPz-+a;yzVqgUnl|_Ft%7(MqVuf;hXE{lHCF2ZJV3dw8A0ZK9=1GTeu=CHDQBU?IYD zYb`v2rzovi+{2bQ@h4?87jd5uw$%IJMg@8LZ1vzM6o{&c7{V%n5d_#@0$C223kja0 zjv%e6ch#8!Yiyzet6(Ps>o6M6;8nan=LVmWkAUisOgL8(UDj`QAml+b0wtTWQz})) zSJ`rn{zz=D(Z4h{djmEwSX!(^ZPaMhTGKdHXyg77DUCNG*u3gne57pNGR1|dUZ|DD zUz|F?3wuqfM>2#Z)dh{pi{q#ASe1LBs*PR_05B!hk@A>Ki}d9}v5yvdfiOihrQ8wUSumgQPT z^#CeUufkXX@5DLrvx5#hRD)I=NS3K=5*W_V>qWl{rNnBGEPPs!nOv=RtGrjq3z|oz z%TQ`338%qxgAOAc(jbx<>pSsBsbK8L>)Xq6SeSZ@BwFdhWMPA9H$=OVZ%8pZ3SwOU zve7>|_N5K7hM2X<8_siH#wcItPcL%K1u0ta&UGs3R;U zDFUi^?@j0u_Vu&Ua)bjE8WCg%lxXp`R{m?P8%2g!!Sm&i8ysliZz-Pe)W~iKi$2@- z%_3*UuodHBQkRe`Gg%(oKyxZiY$9Kkf}%9HjO|Gs??vP=@Th3JlaO^YUi*R06`J)L zM<&jp6-PabbnTBvoEC@yMN~q%Hte32CG^+Hq!Y-3#Bck`o&Ye^n)8gAcjrS3G3;f# ztlv78_U$6c{iV}g2vq6cNn)6j5UD?NVll)n<{W@3DD~vmQD0afGzl}{o*aCRADki_ z=2bm;e{nE5XBgAp9!e}Kj3yT4)qV7PJvnnErUkw1#M->mWvgOe+8O_dh*2zSE)^88 zHm|BVM?!u%g)5yXB(SvQ%{h1(*lmIK`cKw|O268HNamNIhp(p3)}H)Y zPDp#QH5Ayq^3-4%J5cMD$!OkkaoPKe-}-JTT@VzuHovho{+xMvA)b$wYN|zTDK{_A z!=;ipwz8(>5Q?(SiryT8!!Lqar~p8UnO`j=uM&6I*a>7SB%*^ANS&jk`adDWz7Sx2zfof8}0FuZtes9;}u zB+1-Zal>$baBaxDuX&9iE1ln=o-T=^!RCgr5bsJ~CbW6gB=GQPFj?(4`p2#G(oAxe zKV8Tn{kWAQX$9i_OdFVjLG*L=sG>-tI9wRH1Q$&*H~5=?sf z00n0WnNK)qk3fD%dRC{TQE?y+baCD^r9)P~=SLLO6W>vFO;58*F`ox*%F>k6!x3eP zc{T1$&hc9d;0GDo(7-vRvd2`T@-mUcE?7|-H>ONK0Yq}-H>J~aChwpa{&C^2T`ni| zz*%QM45LVV0&)-tQ>Q{NTp92^7BAbrnT{X= z{9VAVs&sD53A%Sg-2258V;u3+r`FgO<8l;^HMYd#YmI#r=S~9KckScO`lDlr5YJ*H zTi?`7<`$KC)kJX=7tUgxcLwDBKwjd8!cf(cQor`?hg6AB>D0=FrBh?)RW8VhP1ByN z)SlFH0!LQ*%68G_C6fTCp&&2fem+vRBmRkKB$Xxc=k(;|r)@Y%0}Wnp#Qlu=W?q%I zCiOVHU(Drsu?a?sn+Gsw=b_S!Z^?s&q(`@$B9FqBJoJ#Xr)3nW#N~ydM4dP7PTb(t zlMfWb={ATW2Afk+3ssZm9Am&uE$q-@f_UMx1Dod;oX)$GpGoCu2*2&EynoQJ>*{3a zoZ^Vt6|5|YO|SfVPV8Lm$x+&q!JI(%%5kuSFHH)rbqC$g2l1>Ux5m8#4#{F8PY=8VI@V4ed8Ja-K;lqb{X!#!&;aj>ZKK?0ZXiqsqd&(KwQ!=z@*^8i? z#a%onx%!-sH_EUGHPGr3#5%U+M#`Q?w}Uk52@(;DP87;v74K_x_RR*0!>X&5ktlO# zmEzeP1rG74R6Zc)k)ZLcZFSRy+?rG@s)+duS#@ktn@C|03e3*a8spHy20vtI^`9bT z_u`f)O#Ei@b@NBgI_(O!s3JdE!u(*Tcut&)y=WsL6Nwiyyej-%DU2D=c!%rQ?BN9R zn<^_3*dgnGGaw`s2nTI<@3*@soU1iqFLm{L9%O65oe^%}+Em03Ncf~gPHAW7B|LXy z0XAoQ6Q0}EOJTxui@bz$6>16rPWHPuQ*dpY}NlQP&(W~Yj6k}hp_|woF2JBV+Dt3<`-hr%Ezr=pxxW7j1 zQwQya#XN8`!r~?-DhW$G7|LP$7=SE~H0T%rEt}55mQ81YbJ9bhyDkeI2OSDJDZ<&H zfCpc7z{})0@Nt=f179eoSpdWVRPk$8P4*5(N=#E;;=Ie`upgiM9uKzS z@x}&0gFt?wmMqhh0#=h0PTsd*lS2lcL+|pf>WYJ00cC2+LrF&Ku@*@=<3Z4k@6y#! z1HMbnm)Yt|r(a~xO`^ssNf!ar*|t-Y`Oe|QKy0%RQc&v8h?=9KfjzMc^aKlRn{_^f zPOx^2NbYUce~}0pm&&~$NzXK7ifEu4c5>-SK}EYd6hM6C<_M=<>z^`Oj3k*G7N#-` zxyvde%Z#-Cp}s%T3I@_;8$>*}*5a{_4bhZ5PS`}wwZ3Xg`+J=Nw~gilc5$!BBVGAY zD&t7Tcn~`6DR*<+%e&|>X3_gVDM4CAw(lkKjiS9|fHYi7ehib9a)?dYa0xv1kYhY| zK1s8QHID&!cPqsnt$usgt_PNiBC$i=EUeC-oJTG8+^^rP-j9@t9;JJwN>$ z4<-AaP5#qrU)yC(0;$ZBDYK-ka?;jB*)PXZ=Ze?K%?i!Ktb-ew40db_8Q7VV*EtTO zdUh6LWukK?5E%5p%-dPvF~TA|IkI*G{jrh8Wn3>JB}N<@nAM*td3w9`L)w-lniZ-u zc$M{GEz?Alj4g%}{#i}WSxk1qGl~wxM_gCa>p1@eM+n3+@v-S<(TCEr%<+pqQ7xQ? zGQ;jyC|j5B74kB3+(IwtKkA%G?O`f>Qqfnj3f7$OTvI!j;|gTIK$q6|JB8Jn9_vO0 z_@W-;zA>)&S=##f=tfTy!#_^$B-!k5xF6oc-c@rjBk6M~M|wHubj3;$=AMofQ<_AOs>}JJ5>u%(%)41kNIq1IvFKc1K))za8*eVg&hY`m|wpzYQxnde<~ z0>F0FV=72u2bV~!IPY^z3hyaE&K20W0xTUoB(F?-BcLgo=QC)WAQ$vR`^$PY!pZ4@cA({mL4nip57 zdCG^p;&{{ayb!lpWN|AY_dYVga-|DRmxFPw@mJ2*&FX8R`r5DPFlu7wmpdZSrh4hXG*R{@B@?OJgoIBda|NU)=bHI zoUCH*`Sx;vs` zPpS@9wL>DBnYNtN0#XtqD+Z<19QA2O#!3`2H>av3C%Z1K->_Y=GO9r|_0?TF(ug(M zsfVgD>2Z;^IabF9Wh7QDV{@_5e`@_9uF=vT!SfDZzgBP77YHt~taOO48%DIb^uUh$ z`infoEYMh5Eqxxb9)of#dL0(3HGTkLB(HK?r`|5C7LpMKO)@-WK;T8j%OIznZiwbB>UnP8=V#ywX^ z#w%pd#G^D3+yFp;7Y+X%**j9Ug~Lnk%jW3BS_}vJqIQ=_yHuY?brm}Bto2{Fs__T8 z>m`%(QzwTF&)35W3APj?m@{JQo40Vp&ghxSY@oCQu1}i%Y^G~yrc>?!%GwSUbZPtE z`JSM$UpOC{HJjhnCYC-NJ=cy1Hhb%;Dq^GT&FVg(_S`i`KL)?`?}%Bdy1Myqr4=Ft z)m|;AP?7ZW#NlI?Tw^Wh|f_hvJC4dygPAxw|6lgr!oKdcOn%DRBs|th9xAZWd^SbKBpPvt@oi4p4n^m-7BH#T&!dE0YfwmPv zJvr9_xZ&mt8a@SddBG5X^FI&lR@2vs84pvpH}Kr*=JYUg(t6T3t2Vv*z-nBnO6}NE zd7O;h6zmPVa$?uX!^?4*Sy;-w*#D+hP*|`1P)`;;LRIC&r<+@dCU=5$4=m8#=W_95 z9$r6TS8#2ZQPdPShq=FYud1yz-Ugeq!-aNd#NHAyp792bt!@mP??z0FA2Vkw_-1e$ zFc%5V;5y)fhG@XskZJ;5K~{qJfOyyR?QP)%$eys(X!`_~u7!y9`0aNY8C#Pqn;O9) zHV(3XM>dH7)_*;5Za{8E&zB~v(*;JqJMNKpY=6-}Hh^_{2F%S6Fae{5=^|BJ@5~Db z;0P59g7!1|nqyvOS9?e&k39|Qw|(EGD!0KUe^x5=>4YiXF%YJxZn}qQ55!Upy%(K@ z<~L{lgng+3LFW)>Wk^rl5&0K-bTpl5L`;>+E#Q^(V$QsaqM_u^Eyz6-cq3@0gW47Q zgMs~Vq_Bar7K}V#VNjuQ?ySq&@jlx>);I}-OG)PvYaoGb&st}{GXTOlRh~YW`8{XK zCi!O&8%jRv05ItdVe*_@YgZf(29C$6{J#S6FL59%7jaI(AhDDH&{8WCD?)$#0*U1U zif=ejaG`mbg5nn$D88S>9m1==H>n7{S z-m<4;{-#Kz1XZOyO--#9yrgMw?PQ#+F}XR?6Uq7(IU_p z*UZ@^jji`;M$ZZU{z^LEm{a1HU~O|wvH0%FS+3Y}66jWgl5kevkUa$Fb1ZQfV^SBg z)~s7uhAeXr{66iM`zERZg8MVJTQ8v1(eKDRRM39wpb=*f=Yuiz3j0JdaH)}79jJ^bPd-8#dQb7oZ4CAoR2{*B&Yq;uo2y@+8FZ| z&34nQ-JV*`uQN$pq=D`8L=KVU&RjtdF$wI!^$qlh=Qw+LyDFS2pxOY(1!G1jS^{~Dde#<9}X zTh;FEOqiNIfN*GhA@?=5i`;6IJ_CnLzdCeZm;2I%{XJa@R#BtYy#(Fi08_?wT%6?G zN8}q53FEtj9)%%X@jGF|;@92I{Rlhb&r_+EN)QjC6Sr;n9EP5^1?f3rtY%N+B&s8Q?}lkqvyO=}aXDxXS++z+i%7g{o)&7W4e~2kZ8xiz11ICtT@a)-*m*yU3z*{=Nj2(#97} ziWm#jI2HEQwIMUdP)B#a3U7HsY_^}U<6QPH`N6RFKJh_Az5^He)_fo?j;zw zh@gUt2+okp1-!bth#+0e5xU$yV6&)&Ps#-YBe`H;R`bHC_W$92fq$`YA~b*Ib^&%F zE>!r`?E){8MTpQlJRni6ajSa4eYlkuxm}>fdS;i%iRaJzu` zVoHGjGV8n4Qnw3;Kxs9QN|dA@uvYS-CyNe3N`qGm&={u?;>Uo9I@p-VH65YTZICi} zv%tkpyYUL^T;4+5EO0h%kkdNyRjEnVspJk^EHGRpP8A3?|BsqLp_1yMJD&4*Matnt zEF})9GZ#)x%iJsQC@{dU(;I~T8|sCze8 zyG1AOj?}ipd5hImMY>ma&++yK-CC@WV^ufTU+RxU-Cfa&ZQMofY!^9?!vuk08i8-X z!H3;e0@8Arm(o~<@<_EKL~0Rf_nJq|Lj*lNz@F4CYw!}rE4LjkRbiCiR@v?34oJWG zQpoHQk>Cdit{Gem*+P}w0L6@Rhf`1;E(NGG$tfH&5ybcVbQndp_T|1j6XbW!L{L z5{)Z8}}E{XmeqjG2}{hcnqYd6KY8b0_hg z==3`dGPXA}I?Psdn8MBJeAdt7-HbEn^~c8I9Jv$g4tHbS&8T1>TH}X8vj{AB8kt=EsIb%i8orF&A`kcVoopxh&F_8Wyi|68R+Du~Bt( zb?es2VHdX>%N@iYi|=tk^C42IYA$M>dxn28V4+DGYHJ2m)ms_?Q`QmPV9OA-g=r$63(u%WQjm72$7 ze0Ht*G8#Mw+($ej>mYBcEOevu~(tx*WziE6D$ESpc{vf+36xm6@}2>cse zIlMZgm2b_sODzAo8N^7&sr4?a^S{NB;0ipkzgCP?*q_f)!xi4F-BV2~rw=afrTkX> zMyc>4D#&IrLlOydA|~`vLP_yH{^J=CSHj2YcmO0l7;c>Yn&|Iv?+l z>vkfjt)1;H{nm_c#XZ`_yGx4JJg6=*iBF(6Z_Ec&+{x-f=vUE9TBt1{aBB9|UhPTc zPM6TqWAG(!HF}DT*5ct;lo+>qhujjDJ^YmQ4HGKH`Pw_5EA~aH8T?~>3-sDHt~}`s z_dt|(V$s{e^~YItTQS?&iArlGFPV!AwhUv_ve~YhALlLLS&Po88ISOe#h9QEBIf@3 z0M`O@!p0Spjmg(R%Tr-_{P2I?6 zE)41(~C3dM|P)!0etmm?S)~ig9%2R3(F^1wW{Mn8njlaS1+%r9>fqN3|z(K z{=R=hJz-d{-7od_&M_O+kYKyz)!77>&jwoxgh)c=(0e0?hOV{I^5MZtIXFTc6&riw zw|NGeM`r5;xl}diekGFpYEC%0xG&TkDjyzhJP^A%TYv_tXdreCUTrna1=(!s==Nr+ z^h=ehU<3NY`Pq-uxm4;*qRzO%I!=WnRFyiHW~T*j^4D-fM1-5JtoF9gen2=YQAFTa zubuxI(M-*&d8bgITl>y8c*QKbdo?S@{T7|}%k0Xa8??rY_y{z)TH`}VQ_NRUu;I%E zVp=Kp=A}IiOUk{+BDK$8)R8}k=I+oFVM_(da~(Hk<03&1#-SPGwZ`}5{nBS*Mar2J zqflxGImm35Zg+7SuwrZ^8P1VQ5DC}WlAC^j!+_MUD8k4TNHQ`+y9F{dCsvzAGGm;e z#u(=gkngQl`$%2Y{jbGtVq8b=v+bdS(qrQr?q5(4J3Z7qIotBu@Pg*h^x^41gumG~ zLO#bm9qxj383g0>q;AW-ZYj=ae5BQ1(P~VS74Lb3SK7isHX69o(!N#5GDx#Z2Ju+! z;43#hTyUX=A2Roa%ie9ce=#0PyTPnjw;JVq8-LAScSGDubE!Wwcy+pv){LWh4~_-8 z`co)iZ`Pi4&#L^pYxy-?9`v^Mj?mr6@zd()%APv0vU4At(j zlsp@LJ8IrJH(2)iZVPwX8nZ(rQU08rcoxcEdcl^v<(t9}dPH=#eLW;#(FgD=6>zsf zIDvL^Q4b2+%x~KEl^H~G;ZtYW{dQt?xt{t@$~5iSD2p>zgd_f`|0_W*Rs?y=AVG4t z%HK8XhbGS_vo08TCdL7=8yzxNC@&@Q3Us*`VdbO{=6DE`KPprlAI|5z)PK>f(B?mR zX0er_&Akq7f^qc0Ex8%ueBeGsk|S;3$M?#c*7PF^K%kCr0}ai)_p?MAP@}7>n!lI7 zdO=|4+Av(oSqDO@Yr`)ONmgZNw0U0nrRk_paq&R?IB`{@)0Z$+dgo@@3t)h5>$|r= zTY^A(e{mIo3DVQ4>B4N@X33L)Qjh{&FV?;#!cF?jY)`@;2I#sF-*HgtpwJ<0CQ!(r zCh$qj8$mw%=D#z&$4+AIcnuGmuiL)VD#)|n6Q5xHmBSKeC$hTKE1cSu3SyTv`tOYA znQx^32l{xHPpNas#I7*jdXyA<%&Nhv(|=2ObuHwAfkV6-uFu@zi&%j9K{m?4T@p<{ zDBIin-1uqOvNv8yYZb2&czwn|v#CwMQt_(njX&otF!Qc=WpCs_0}^;IYWB$`tI_1l z6=V|_hAi+lcTDE>u^^*V8{WZjl>Hmc~ zud4Qj{MbT9;iS(A8eio8K7#Ij)>>6V0jP_R@5p5JLX8(S|R^)bin<3&Qf2Q-fdM;3B zw|UX(z7!dZ8;RvQ^HOdplAFr5@OL~{6k5CSHg&GO+N5IX1s-JNK|#jR1+l7Cqko|# z8Q)Yv(Y7l+#lF(J3MahWW>{jb_GDYyt8Ln9O~y)rxE9YF?oQ|0EL|rSp781D7ulSM zx@KVJE7fbc&mV907pvDkYj3xjm=@zQECfxjKKNb+r~yl|V>ud-TmRo;y1(qibYB=; zJ0zrgB;B%g(R2J1iRd2X*q#4;ne{PijDW7)|A%mHWz)&}hbyr!`G?YS>T@pKEgOmH z>1g3m!MSi#7aUD2{VJY&xk!ymv8psU0p0NDB{<#kSTGRF9VNAp|L0lZA7gh`7jv*A0o~-iX{SMpf8n=K!@o0r=sbuuu`oJEe|29ViRx#awqL9&lx8u_+ z@!Yj4o;zRoQGeXIi`3{}r8TwFP|I1APS3TwFd@mG$H9KYK0?Iyc76Aev>!wW0@k!E ze5MQRt`L7kCm+3^Qisd7v+L=p`)DT{)O}zesC$VM)QyI6@4~!mh@_fZ9!y?yn2`8u z(pP5#xewf19UhTJHg;kbtv{WcK^UYUo;1B%{6j;x6$VrC2PFkTPUyBduQZwo+P32P zLLY@I24c6*S5qskaR29)fq?C?PQZ4t${P}}t2&wPgk`pVIM41Y*2O-h)C~|XSs)#>ramEx4ajCWvW0r@? zme6R~dlbpWX){LLlK$+s`iXI78+uHIHOn%e%O{D`4wd??3y`I#f>bf<52 z4x;$**dbn0)ln)#D3V@-my3;s=YC4t$DD5SPBmf>P&mty~Xa~TEJa`D33TGJJrR1s&Z z_V1c?L*r~ka1bY=zdj^L{aLA>bxoYD2pEG>_M&#^BND6RcWLZwewT@v;P}e;ql%TM z9|<;8E{hkiHA=cL-3(_aPJfGEzq&>$xK{Rz1KNy>yCkG(g6kFvTN|L83hX(Ot6G8mRfCXYg@Ff(rQ~?S8!`sgy0Ie;ZjYlZJ!vmu~op0{J-bk z=b21Gu=ag_{q^(y{vEhE=ehemcR%;sa~WJG3uH(gFOV^Gq`*~lOM&Q4@c?B8DwJ03 z^E~v7o{p^5r?NCU4B22Yb6441;okU+RW3_dY|64Xj)v8u*Gzi8M>!<(SESc-@M_mV z+jm)kQTEeDaavkCyd7 zcv*PIk9h4jBY0cePdGc}9;KX&9d}2j_*L`%%+uBrKZV?~qEEJdrX%T#f3_~|^BKsH zQV}5)#C$R<7*~#pKO~Jr#z4;bWzeO`-$S@|jy#?gxeMg?IOlfW1F~Q5t1EH4zcAZ{>yl zn!Do*d3B%=tMID>F(0rYOw}909JXxPlvXx-9~{;XHOO9%?u>)z2w<-_*!s!+;Z5=V zpd@TId-oBN?HBrAjja{z@;FKM*v@W`?Tb++FFIgPyuTW3Z5a(G+DOFj2*%c!I6gm&sPu)rv`%3$%p8J;WdZ_xb#PsWZ%U97u#ii?3=^c9SA|t1)zbi1= zR^vw6lx8C(oErmNGnh9hBVC$heh%Td?&{Hy~(g(7P z8mdwFWBuQZSWDA|mt;46eN?WafeJ?JQQEO6R*2L+!KbW-h*{wX@CWN9fnspe^& zRJUt)wh5y_vN-|E*1B6{0Z`#tf0^t{v<|1qFnJhi-a&`c;TV{342w&{bAMY3u03^G z&2aV@={iOUoKQQM{YG|E)r&unHz=}gWmfIq5lvQ%P%<)Qi&VsjV%Z9_E}1aa-q{^( zyPU=vsV54_PIQc(K$q15N<-_hby=n8*ksv%(@YT z`^ywm-NQ`d>}6~PRc0SUpRayGHsLu<<+89@y+-s?!Nsf?yHxfyLf)^pU+HXY-dTN- z_MM&ZXLzQO3aXwRX;akGP)Cbpp3RC-QWb}isyJ5S70^JnZKBf%Da}qtN9cQ;J*{Gi z;B0#SJ({Zeil(Z}W1e|DJ`xyP-J7DSZkr#J9`vH9iree9rm7dTG9Z6gRh6g=)2gbn z*Z-OJ&t6a_;_QqG=n~+Ag9_ACWp9|!_VH(7Jyqx0daAxp9cCUiYN|Z*j?(-6J+xFk z{vuI0TB^$MuD3vd;ma1=P zPcKAz(&N%`TB^30#)O8d_E<9(%Ba}(?x&0d-L+LMZTr+%Mrx~CYP415X>C<`+q|?a zsZPBQ>P=gf-pssg&1R#+u+gQh3iVduUC<&p#-!bgwkkVx4539>@kFYs3cIPQdI(tp zVVCt#RaL0h(pDWilrB|O!u4I%K2ZY>OJy2u9}~`~PTr`ik{!^m@6}T`Jt=Gb!Bv-Q zbyb(>ZPj+6gPqyMB%qrnc`!<-Bmi;BZphQHfB`{vL`T=La-#J}PMN@&uEm?JwQ4$^ zB6MA~?~pnBOI29)Cj@iQdkJlEV4@AmC`Rfhv%febwtc_=!O)Q0_9qZgVRc9>aPo+j zs$NxCJ%o=Fs<8S2ju9%XHp*u?bTCS(zA2w<%I!}Xow}>Ax*VG(pV#=F&xd5%=$({_ zQj0gOGW#E+!b)=~tY&sM(5&q_hI6BBimj{O+UNp1>Z=g(^E4t|tU|{)Yw>F#jqcj3 z{B5j=S-a>hj=$|`omEkX)vNX@z1v|SC=@i>tCqCM5lnc~gH|kO(^Dtj{u%96i;2|T zevw4oK9|3)_AIHFI9M{Gy=tnXx~f75<7{}|HYGEQieza@v>`1RCd))kj4stxM}=w# zsrF&j78jg#ycVmS{w^(6i`GhKz5PU5tgP>F=3=i{&%a4(v@<*Xu3alFDHqJ@ygTo2yml~HLyoN zi`qP4NBeo%JU|@U`-m$U#u|4IzHmkPN+?rb4zm^~w@>OpvOs|-EHhf}gz zVR>kJ5Cm<`uy(rWkvHKW?JZ`&@x_imzSujX5WtEk_LEMrO~l0BmQCN{9-HT3WUA!l zn1jKO{D^#Ur>(O^;^oMCeRPs=HaFl82l+K3mKgzOurL9Q@horcg_$yhIQ#Isxp zle>zYDHmUguVSBeTdmXpNL@+6XqXZI93pA@MAEIZ{^duL_x(md=SX3igA4Y&y^N2zwh!*J33~ ziMY+t82jA)*pPFs297w$X+3=NF@XgV!EG{zp;Er7+7+1OFaAK&LS)UKe@4g=C!ye$ z!oqw>ri>52ujQgIlABaW$@`mz&yl!-4-m1|Pf3(_ApVipIPMD4;qjrpv87L$JEw*+ zS-s1~cHI}uYoxZU{f#258cG^O&aHVSMmKodVKQvjKT>+(Ge}`ibf%m`1);yqTqMj} zK4T;YveJBJqy~>T$OjYlV&yNkq?F}P3yC_Ul$<%DCWfiD#Tqg~8WFd$xb5@DuL(~1 z^#Sd1XQ4J9fyanAOAL(WDuY|}V&^7XKfI>16UEp^Sn5%7Bmo-dBqN|nn~+=h(%<|c z*SZY-AjX9HRjDz-aiJ{lEHCQC11Ymc3FtR#w1Bu-D(eRb_FI49+~XM{lkO)pkT}pC zKu_mB&?WjnQ};|G!{3cITyWwR?46IxSc$y9Tq;6>i7C$?+O%2POX#T?Gq{h~bbYgY z@!o}8@_Wzu=H=!X+@nR9SoYa6S>}a&Zdd_mALaw;%-CR3USqBsb!wk$Fd?$c(z*ZgJO4CKn1LyvCd zE9lu1~A_lJqhsi*}FsNpRhl#m^Aa2vrXxGMQ6#e}ra*+570)b|b_`z@SL`P^QwqFoi zU8V{Y$Qa=!bX~*{L2XiF&sz6NP%}i-b`23%jn;G215qjF~p89@W=ICI5n5pk)Jv7>LOEX)$ zki~kaGY5aXoV_u6L!7^Jujiqu;_{sJQm&pI2KMxTYgWVIz%X_Xzs{;V<_+}WZ{Oe@ z5=q}Z=ONMoPvq&Thar=v;g95^E|c@ay3D>o9!uNR{-L&)wV~V$;dP&xVag&`kP$ z_QWlv43cHmF747h0`quh**()6IB#a(z#Is2mgfof3VxwZC#B$#o{eO9moB^nwCT{E zfD;7SC3czy2<%-V)nU>>kWZ)6HV8X?$%RW%WATY@# zgvUbDp9A9=t(>>9Trv0TWoUb4PwYncChS);7D;;>F$&-Q##yfk4;6t?D2uLk7}N4b zlwa?i;HJY4bxxTcm#uYifH@l`u>OtoXMR|_)L+cGu^*K~wHKil|3iP~ff}ayr>t>L z;@?a;8F@{-AsdcYPbc=-)e2(G)&*^xHIl6OsPg9Q#t|Oy_Gr4SP=W3y8(H1xPrNqB z;(e%vdTC&i^)%?76gtFI%$cz)EA^y&IE=j~lWGP6iUQO92R_p)p={nyL30CEX?oJ_ zOzB6o%#2jzMbg19KmyU89ep|m9bAI3G}UXPityU#g$26XC&=a9pVo@7%13(s{2BIK zHE73y+4NSv%qT}uD;yClb`E6}I!o@z$lN8>?B#CTw*rK1npFqrU9X6ql$lUjzea|; z+=N^56~mcZc>YlA-M5e)V@kbr|-c!U+6=&ZF_U9RBW=FR=671 z9?IIVc8R}nZAVVSvjKPG+M~XQliTC68%vL7Z)9x9KV&^JR~n{g{i(3}waCT#j$rbU zJt`}XA!J6*p+Iy_{1>6;jQ$MR*s9q#W*({j_BWW z*U8zFY*btD&oOWvAo3VEJJiuWH0$slcfd`OiX`9ni2!9*J8~Hvq5MLgL2C9rP8IR? zRdQgW{23#EhRPpL{U=$$hMdff&?}x>c5?n7I)HZC&`a%coQ<_dgF19Xj+6|+v?ogovVvn4w9_vgQoKGHGtTB|qdh>e}B%|#|&{rSa#^c6@@d6V~_LoKT zJllS5)g7{4BMwU6+L`hWR;=}YX?+W;y()>)wBPQ_d@|U_SND8YdtXuU5CiJ=hZePl z60AXWgwz>+jXk8vuq~#}Tk|>bM5XB7Fy_6}V&bM*zSpSBc{hsx* z49{tR#q|rCny=yGKrob$gF=j_I<4^t>NMuGNUaXF`jEkO8R9#TPewX9fozitWN52u zTJ)mH!}7+pFIql!oDgKl^7^$eo)k>xVnz%8zndlJDxHDd#4gjc^;9d24J__AL3I{J zlZ8j5M{ienU;npYQYh!pn4Q6xgb&-J5;~~#oiz73vt*SSIF;=bU^HJ*x;tb6M)4J+ z^j0fI1xI9W$XU`pWV^g+XSbMmZs06wkCEZV^kjs+XhS|8pUV!dZEjrK;#vPwu|PtP zvNn&|L5wQP(;#Akg4PA9IrdpEOi6vWp+=C*KV6mVtN%Ras)_uKY_0zn>GhUb$C#XgCs79%uo<^bz9l^Fg+6P0 zkzCA@`~*kpv>BDG^tbF3Qb<9_rMF{F)&>~Y_F0rZu!@pzK|h&4)t8 znnHOR{%$OFt#?c}1q+_jCK|6GhUD7!xD+jvkXyW)u-rh5ZONIi+sZsuw;49LvgnF# z&B=W4y4Tv#WxlrAZu7+n*&9naF_1Ryt9$1`PHihPR$HW4OMwAJ^|yYtp<*SF4w>HypQ?1Xw6K*2b{e%eZ(gGp%9@*K#HV|)tS9v38 z6?#p5M|NCC1S!lD|lnbb=G&6jm9m2FO z|1J4Hi0IFlx*AaeiTaCu510{lIxBQ*GfpBn4s+^x>$~C)sY&~WX9J%sWt|(I z`O(AQXphbd{hr&M8Dp=T$(1-6>m=aUbS#|#9c6xGlv&-QJmbrwr)avT&b;tHG?u8DGWYjHP3}*Pi2Vsu(+#OQ@>`a~W0csd14u&hrowoz1X4+WRq3 zleJf@EnEf(wTLd-$C35yd@_^JYxa5`-qW7tFPd>+=# z$Mg-{RW#$c<&Ek7`Z(CQdZ+XX*|W}=DJ7@*i@0HSi4;;R=HpEsvsrT9vJUT;e)~OS zni0MsSORjdIUxE55;=Z8*e=0IM63T0*6Q|e>AhI}K9_$+QVFX&dLe6Bn|IQs>wJ-| zBotP(xeKGU&>Rd56gi-N*)SN!(YXULh!u=7d%Hr}#+K>PArA>v$u1f?S&g^KiAn5o zIWf7cHD^Zgpx_wUlK1gE1OcM6GfI!@3lkmoA%Z+hlDhBNvOp%jXDb@>}V@1N_D7B(R?s zdU<|rg)86f-V+^Gk0$Gi}*&?0`6a2LTD zJI}x4-DL0?;FE296!;Kh9p7*`xE-d7i_XR0WBTtG`tRrZ?`Qh&r~2yHO~#8%uPK1HsL%_q6bS${OZwaRKaA&}0M`Jw0AF+etMWz42&;qb&| zAE{LkPg^VWqTnk`!Tm>ITv2co4(6SioSWHlHIH(eLdW~Vgwkby^HIC(!a$UHo&iwp zjdsdkEMuk|bp-l3<=>SI=izl3bSfir6Fy=^e=-CRHJ*W)p`2=RM8;v@a2N}ZiNTm! zOOUeYt+begR$1P3&}{+ye^Atu?V5*E8p#(`m9y< zb;&1akruWdkk}f=%1SC5Rzx#UJ7+W8 zWRbxP9OV!KG~Exr1w7AiJJa~w%%`X*dl`4H)&cJVs0qWhQ%12|Oi_Q6urY=k4K4ZstiwB^m>oh`)LT*Z%PWU>!~~LzRg8X%B}UY>>}ZP(USyDH zc-Od#!V+6$3(r@!#>sM<8`HbAz82EZ35W)lzl$XbT;%5&$#BjO)Y0eSWpzDUBFqad zjF(lI*Wc)C%@Z{)q3n3>IWL6kA$nbW9atU>zDQyt+rGgl92wsx&LZWpw3-LE5ux&= z#>9J4v*WY;>vq)fO*UXrwuz5zS$yY(5>0w}o?U%0GXLkrCre_feC8&LU8>l5#V(C( zWr=;O*jr+6GKK;OY&*pEXz*9L>nuqD=@S8-ddZ~GB(t5$Jih$UU{h{1igCJEkiT=E zQ%Aaj{Pk^75tXDX2)meYB{>yT&{aY8ZEm5dCY&o6uAn$mK^*dgllY4DlO2ClDA7T} zQbDQIMY2>7gd1d%@gdCEKlqZa9v1iA%d6{$+4E{sKh%X(OSqa${p^USpFBG~q3=br=F%riMN739XU|CiOzBh-&#iTr zmeq48*KJ+%HR=5qBwODwNUBw45U+K)LDH;?4U%rtyF`QSssIASbYpqZGCZxPJEU1kw!v7Gs`mg2EpGj_$I;k8(hX0Yq!BS3%7<|9r)doK#c!|MV1z%!tOYl5{cL<(k@S}oH zGq`Yrtu%wX1s`s3{Qyj|!BfRP#^7GTk1i1+m?vf4Gq`@yrPbgW;^#$!%fj1gF}U1; zwH`CLJP2cLHF&k)KR5U)!EZBoo!~bbe1qV12Hzxjz~HwDUS{wz!Iv6*i{J$Y-zs>v z!M6#XVen?bPd9jr;9i687krSxHw*4I_#weRU#!dCDtL#%Ey3S0c!%JJ41QGbXABO< zR9VdimuI`J2MnGp_!fhw3Vyr6y@GEtc$(l122U4!mBBLvuP`{QSY;I&+%Nb-gBJ+y zH~134XBxav@N|Qh2|m`~)q#8tO_fHx-Y=jmH!d)QimkV-sy`(y(zG zn-3RBu`l2S!K7n1=xn}aY%;L<$k;q-j?C1ieG>kSq|d7-Cd4K!?{Yxc%Leb3$*yqKHjM77v|WJerfgMZ%CwH-dc zX;9zg>)!74EMNEOQP0&+vj|3sBTZyy@OQb7INRsE=!5?H4hn|mx~V&J*Y67KZTI+x zvEe(^xeLytta8{ek7tuS#@;XwlMS}Dio_aWRp#ELByibxJkiatelP`ak)V~`YSWy3NOkh&|yL|$KJD&j$KjJV1E{YqKx(^^OzN!8*cc6d$ zX9M8|1H0p*>bEuoQ~p zj8IY|M?0Yd@EE+I*mdC1Etv<_p2nk!T2u24n+brBN{gG97m>yHhLV=xsr?1(RnC8M z8)L?jvp8~g5`x>mbK^PlEsjIKCuxPAM@MjbY=~<}FJ->P!&PLtFIo1iPo)XvHR}9k zzU9$u$?Qg*%eF6M19?>Mfc>7?`~A`TQ2|)fU;JD|-i1}v96U+$jG8WH8hyDYSKOvcxr9gL-+`{B zrr}5Rk^b`&iM26S6l0;`t20F|H~HbfH}T?H%6-PMSUbKcFR z81cflrNl=)>t7PGG$sAaFZ9dT^pfu7Y51;mt)`S~aL}c>LozH5*XTaSUGu-5u6_8m z4>)+S*Ai)G$|~_FchR3W?#W^I<=TCTohiwVzZDWsV{9s(&}|)x^$5}rqz?!>{o^Dwa$C!grV3o9vo=$Lgp%IBNkB(u z%IP|(R#C|{QxZC>^JM|BSK;yb^eb?3@h3yG`C#LJOf0_67x5Bzm^%VUW1|%yg#(^Y z(mIJV^ZCFu-pvw$G5nm0T(4m~j>JQm?O|YN%7eBC_R#YB7=A)YBI4Yc@*~?NnQI5I znNW15z0gjY9ahiv48usxvYph53A*~8(9C(zhxUuAG_s-p91ME#!0Q$JSe%fv0pf`Iy`k-vUY&tiPqL?X zvbdHFYS-%QRTNw0a;_E}ofZE#A@+KUZ!$4dp*1|c4o(ssj&>wkjNm~aX$iNMcV14@ZI|{H zteO#9yn&@U{r+j|$KTficN6^epS51~xY&fSu_`(9-m4Oc$sEe1%lMrkgUjW+tc!5e zgK{8^X`#jX1dbAKLcU~WI1ZN@hgR(%0-TSU^Zzg(+AFW7aED6TPGE$v?$2xWANhN3 zW^=8_`jB8w;_b6g-wYRiU%+k67$s$3wB$Xs=d4%s)FPu#V6f=L>+hd{RBmFN6nK~Q zA^ONfNwq$`Yr+CA|pKr0h>E5yX|AZ((`Y_fSPl*yW&O<`6hpr$o84=fePl5_C zaAEblI|_9p=={%tjKW&}Qy)B05hJb3$n&TS>r9<>y=?g_8$~(U+kv0F5JIzmL=C|Y zZ)J4f@p-JT{x2itfeVp|Ey%yJbBS+bz>^`fePLGA;jI0~kn)bwvfi#>U*yiT&fXvT z4rhDNs-1*Z?WeU??I8oHfTyh&-;zr7G(5#-l0>GH$oZj|R=mf_>Gl0sTV>q8Vl3wn zdnv2JW@#f$u?hH`amgUb2{IfW&n>$;Q@%~zNn~pY1t+^N;^&?Q*%BichZ7V)-sAVM z`bpKsGH=pT&i!vuH0x=%)GL8)31qNbEr*FT7eaVPc5%> zpSU6JKHQejp@j%9+xp|%wukSC2Lw+t^xt&FptzLtz_Eqqf~G!ooqABDH)4e{92UxX zMrX>|0LWzQKOtB?ny+XZb^=4+M+5=f4>c;9Ej z7tu5vdBuH+=f+sr}mV#cafb!(7!3=m#mFD z_fnX*eH*epc{IzneS5Rx3ZQ|aZ|1dqqFdH!WBEMP_8uSFwjBftUrA^ogl_n>2W*^$!WUD&UoL(n6bH?yJyA+6E+Oy7Cl-d z*t+q5LmxrcebPxks(H>oiW7E!(|QSy3YqK)OrF`)cT>_IS*7|zi958qAz7j8nwEO^ z`gOEPNKGP&=L73boh(8E8x%Eb4b zzCsCqKgN_WpON=OB|MFS^ekbfl(0Vzx?I)bW1CPw`Y4B_T@^LCdx;WhZE~8UMWaMK z%03I?P-P1wuh|pXqop@jPoOUXq#rLL1;pD$P4W*WphWe+QQnqt>cn*J%P0?e1f6Rp^+8hqunvz;&Sx6HQKa3hu^Pxm{_Jlp?Umh)V2_!_b2+z(u zcHOpiR_segNsE@x6z*V}0y7Ty&>(SrGz8JD28qn_-zOuCpD~#2Ct1kRYrW2tIXVZ7^q;c=qU}w6z5VCR3nEV6wuJZbuMb_Fh^uaF_0jc?m?bbGyY)f%N3*m#X-rb81yl(n$b5OyH4h^jj z?;S>*F8#NTsyxwu`zS6w^xr;oqkHS{Nd33A(yL}}@yzu+)X;Z7uD%@>8n5(9>nI8; zWWMo*T3Et*8j8u8h>G9nHgK8^|8CpAX~WxX*gzIUq%yV^w8t3upxNUace9#R_-3US>Dy7DPR zH-)(8{clrsI!>Z{|SY-y7{zE zl2~;tT?%o}JK8P^aRFh4xZp84q4Rh&3#GaLe^7{f&ql_}6Dq_-9x>@zw!oTrkqU9s zhtdxIM+$LoB3j;6PL+6iQ;54@oX!^J)DhX;)xaF))?PH z#uF>V{p6=%Li-~X;(l_LPRdb;YgD_+(m1RU_xThA%r=hJ8gZwykYvIM#QW-x#-WCr zrP-G&$h~>GS!8~hg4|gsU@Z$w;;*A1cN5oL-cM+6tUJ4cI~AQfkN}=GnIX}UEB2_!we3-nJ4x(IQ1C9W+|zKfKvd)o z7Kn=6egaXE+eaX(9OYh;s5dHBKPasgRLU>A}1PDexrbo}5QDqzeS^fby<-qp+v|cr^tiSI#wx0<1w^RUtBPDx8gX9O_ES7s zPhJ*YIbNG>tH}N4;mG?&EYL;JRWuG~upaoiA1cE%;+@V$9agpqUSN2^Q-L6iU zbJBmXKT0Ncwkei{jHg-6x4{Sz-MCj}&dMaM+RARaakH`NZGR*eT+%3S#Qtc2eh0L$EcL`h|cCwTyo7meir45qW_ypeM~7y_JZ z!o4-OO5no44Mw7whm8*g&6N^i6-SLi^G4f7iHoo3`o5hAKhi0$yDG)Hg>ww&z#wln z-Dp=k3PBe!lIOQtcTY99OMLa;9Hcz!g{{VA#ti*NEh@III$w@_28a+m&$Pf=7e4g2 zzD+Ychgi++4r?lC-P)rnq~tnE_!fw4nd>A+^}7o%mwhrZr4v)|RLez(rprgOeS6d= zO?WMLNMwkL2;H`bZ@5+L_4@3MX8XmI5|qfxsj}$AfKM?%H|l})Yttw(<>zSf^}rqQ^MA}coYYVK(Q7>GhiUuc z${xCjvd`w&MIU}pfKRhb;XMsMXINmy2i-}^sUw=|1pn$$98FRi2rB9+R;a;6~fxl?~TJ;rMl$xRda5T${3Oy zd3HcHr@kNhl%wU)@8x_Z#hQLecs%;xTy`Fx5_w)|6e>%MdX`6KVIhaWG3nCOEP4Zc zd-0UnYP0|^pHUX&4^3ZECd?_G@4IEMKXdwgzJgU;s0@9;twqtX(*89#du}e1&FB~W zxU)H|w`<`#p%2|cPDbPn;=b1QYjjo68JYvb{1g7l*k-L~rzh%nWP=ro;f$?0Xia_J z-#8hPuJSide|3d)9@zT7Aa5Lph|XG?eXhijZ9Vz`F*e5TE`nKf_5H%GU%lG8>pso5 zueQ!u;?O`358-y-b@osD&mp!Lj`!Y@q{lS*-PTEUI?{PM<>mmKq%`PIU@{W)YAs0C z$Jc33XWO2BVmwWd&(H_br*8Cz`s7b|&mTILd*BOsAgwyT7?G^zK+Y3F`h3yTwO=aW zy#Hbv=Bh?;sNA5NJ!4v#r{NBKfF^>lzq zb$pN|ZU^7_g)Bk$*;kFFs=e0BnN0oS?Gody?T2{karT%c2aoy=41CE?U`<+E@hn+O zlbdqBhBeV6f+J~4DPrg4v@DAOSKpi)vqz59DP*iZW$o<_9b-s=3?DLb$R**>0pE6R zH?fFs=9V4@q$r^4b<9J@lzrO!?$l0sSMxj<5-Zb>m|=n?NT2|_D0xvAH7I0QtdNQO zJ(_tKvOPELAeGLPRQL_P-^s+nJ=g@#ux^GYXpUE{ZwY%4mtMy` zdD-kT#=b{X9jwOZtT&0DvoK!6%*}kuA9^XrlfM`1d(0Ud7u{|%Ik|RN`|DOdG1q6r z1{16?I=LhQ`+2%b^zuJvamYnhSH{cONPldZdayI)YQEYRt-cIG5jmdDW*H}iH2NvA zXgf!$iFMgbydF8^ABJ4ZTij0d*P{@5ob|{8DVHQnpw}3AsEltK@!{1nR%n)CuKi>d2T@PY-k9ymfU~yL<&J9ht@~pg zsbzbf*zY^=DK|Z`I8|Q)#5N!|KM<`AqzObvgjXQiA^fxJ@?7pZ4#J-1X1&T-$G6IG zwWs&6zh2u%wWs3C<-V>x*>NWm*ksh9a3>h2b<*&_(vjDOHIGxx3MDOMLMqg4%m2u< zG{pMJd}m0u7SG_YTUf2_@uAq!aCI78P`uu`56<9JF*em1t$8(4-nZr^QMU)K7yX6e z$OG3;c^em`w#}qp_VU1WdywMw^1$`3MHICA1J`3eavIco(vn!eGQfG;himmbayZOd zF+21mmL+5T*2{mEFA5+U{qO65&=u9G-(S%t(!U9u$k=_u#4Agc&UD^ zGa+fiXkX27H zll;60td$0~ShuqcVcI}V-QM<8lXBOjVC{hjqV&=bm-9K2MXRc$TmK#(B`Ad84-00! zBIKOUPopJ*M<^S2;j|FIWpNa_G4`${Qu5t?qnCl{`BrVg&HY3nNT5$=N+?!)N!!&q z&I0Wm_pbgc>~fOi&LgRM{h@bR*%w$JOb}s2b~jwpjC9GeUhL@tStLxM^@#0~9vNmk z!=bWPtm!2>Ct{ZaWhL_dg=sbxtI`?UY(s{cWdi36hm`YjV#_nu1YR2SRS^ z!Fzhk4da8dp7>^OPI}yycYu#0iI%6cHuUPGL#>Q(>QOw_6w1nva1Rr@{_#58*rSS#BR!2%5`H^JUW8LYM5t6CBi-t*er=)B!pCRzmQ8EXmAzy>l%Hj7up{f%TBR9RMK}mW|MUBQmIAG3NCQ{u z0~@L-=DVK_(`hN3LD;F!`p258yoJnVXF-f+t5AL#Gh)z(``7@hIuwzYQrmR zc)bmOXu~vFnD85H!#*~A?<`~gk?l`SGvA3e9BadwHoVY=SJ-fa4R5#MRvSKL!#8dC zfenw@aKLnv&M7v$(1wLJth8Z+4R5yLW*gpX!-s6R(}pkF@NFA**zi*u#-C}@_1f@s z8=hms`8NEz4XbUq!G@b`xY>sH+VBY*9d$J8PZ0NV)*KN4UhBw&odp7*J z4Ii-K9vi-9!)bOs>dNKMGj=^bWWz&Fy*eIF05^{lrEW?MDl)L}pn=caZD7w}?$3;U z-6_4hNBVaqeXvZvWhs-7X+5lf9K$B+5tt0KOO70fdIn~UFN*aWqGWIRR0(`9SQqm;?N zf}WCJu0`s6O4%h}PJRrmb5 z_^R#UZ!!5O(IxNhvJl^;5x(=Gab-l<1-N(rmV7wrDq5MOr<93bz9l{>hr}cKmhh~6 z{AaIRd3J5ML6z`3-J8$PE68eo_##~X9U$&QBAml&o8Rf zpQNiuOA)`st%y_N!&DM}wIVKwN6jr=rU;`J6a|7cB{=Y#TT^ah(4{O`Qycz*UZo|K zr4bejgXSy0s#5z}5VT=YK;n_`5=P-q;YZ;vNhnuTbWCiYICtOpgv6wNp5*=m1`bLY zJS27KNyCPZIC-RZ)aWr|$DJ}h?bOpIoIY{Vz5Z6Eh{c5UB05M{E90pR#sM3f1{>0 z5WMQ@RjaT0=9;zFUZ>_%)#R)y4;0i?6_-lwuB0s$Q};Erf>Je!mQ1^kQj$ap5>jf{=b z56da_3cf0J|1H;JTV!0~UQU|jxL5G^8rz@ro_O86O#I@n1ovX?Ek%|D6Jgeb?QlKSvM87ZZSbtSekQhK$|E6Kmfdw^aorI%W)CB_Qvr%Ely zPU4d~bxJ1VQx}~kYC5eXZ5dN#%<-x;W`ttCYSgKGEhoN8zNO5PC$W*1AoP?H9Z#uB zokwXwW)6_@Nehb%nXU6Aqp9R;lCE88PfmSL3DqbeZN0_i)ooDPv6H7R z`c6@2h2wMb^VRC}YSQXG#op`G&|wOrhLiuVo}Tn9>9hZx^rnZ?tEP>bHgFYj)extw zIx3*r@jc1un_U!h@;@yc-&fE7<>Xw}N~=gWKpz$gIbYHuom%Wl&8hD*)QoU?z14RW zwJP;xMndV|ReH3LQL~gWQbw&(9fQ-39B9gOMvwL+xsn)Vd@y5MC@_T%IE1|lKfkF|&gSBdxJJjbsld zzrtj*-;$G6{j?eC%Xx7YqY$^PD&X#8`vLjSVtZ@HWyzm5ds&J_Ut+hTu@w7*;9jl0+WuC~8N z+23_;()`k9?#x3GPbjc&-~JeK}L)U`k?&MDuWdjps?}#aHhxMYIGmf zCn`B6CnqOXe$&&5OFVir3YNsV)miE3iwoeNd%e1exeLn*`6;!kdKEu6K6rV-?FP8{ zC!hcMK>_b^|I!!-&A;Q_j<@ksGhgz_+~wSSQ@T(7$RMZxp=D*v4D z-v6|L>tB@XtNnArAK#+?S(|^<10RkcF}imB>egLf-?09MZ*6GY7`n0Prf+Zh&duMw z<<{?g|F$3e@JF}*_$NQze8-(X`}r^Kx_iqne|68jzy8f{xBl0C_doF9Ll1A;{>Y<` zJ^sY+ns@Bnwfo6Edt3HB_4G5(KKK0o0|#Gt@uinvIrQplufOs8H{WXg!`pv+=TCqB zi`DjS`+M(y@YjwH|MvHfK0bWp=qI0k_BpC+{>KcO6Ek4G5`*U7UH*S}`u}74|04$3 ziQP4W?B8AfSk8mxfZq9y;9F$LoF6iZ-M*Xnj$BLJ)Z?4mzunw7_4wuvcsKW(dwhSl z$G1FL8JV6uYZ>`1(kHT}ZpO$-{CTAguW@mCWl7c53j#%fa`>UxFRCrAnYZkU(&9jF z*`q0Mc+_&!}WE8Vq;m+tzW+$!l$R#71V7|Zk0AZqhN6z z>opd21qB-j>P@TLP)8`mvaYPG%X6^@^t?zN?XK!meeS#+g*)&@!_eR(BCFW1F#!gsk>1p~c#u=CgD4_bbS zzeUuG!zXcg%f-};a3_RUA-hr8K?uJ?ILLQ+pNIj<;)4aPup!stnXrRd~ya zDoZL#YrH+n*;RilN&{41dB9s-RZ{A$TJEiOc=Zy~B+^}laek9&Kegm&GVMTeF&Q`6 z)jPkORn>Gb(=trW6Yt8E6X0`$Usb$wOqb8}>qxrm+(r5?Db-CO(vLS-D}-6JaPCBN zVjSsTr#yblcyEzi3TZ`=p-JI*|D(o3+KP&*t0iIy-J>}eq8%5mdyV!;rI&PyYE}fL z!fU;0rB^Xhl`r>}uB;BMKJ_1`w~VG{4`M}Rw77`Y;524wu-=uWE351y!O?b49IZ!G z>4#o*ydC_r1=$O3T{GeF-?yBX^Mk`lj~;vLYw0eEI_K=AGC$QWy_iP0dMW2+GEvno ztu0?!T~T_uGY&5;DX$GI4V*b`Qgw+Lhz*%e_*dfYKhUiPmL#fy(-PFc`JVkr%?Z_S z%rWu;cY2k25|bqY{rsNtD)lDD`R;#Gj5=w`;OdmZLFp1k;@dY$slQ{sW`}VNjaNeh zNopu*3|*L@hEC(VCZ&1k#H8sXcYD;ZKtDC4B#HDBm1k;vO`q17{ZYcqSi>9$aK*={ zc*5XP?MiT|1WM)_6t4zN^Qb{nk~{jfChm`Kc2~z0_9^HuY3(MB0I;MlX}Q(V`6>II zytSOJ)E_VbCvUv(5kq|ahsUbnvs0T*NtAN@Z|uz2brSq&?pKBo0k!)_k5e?W6`fh#p$rBZLH)LSZbkUC%6 zSN9*(M-3`*QwMQU2fDpTxpHSJwFDC`SDz@=XMWU|){ErtGH%9vgn7r#PZaF4AsFYo zHyRe7%Xu-zNvnVVKB_-?>_0_XaD1Udt9!DPdLHxFFGz@AU)`Sis`&YR!uj6j<4k?F zQbRvC(1o6)L|1?1@+K;8Nq^;Cn5?|e#alDHMYWcpDQj(#kqc@`;E{~o8&%x%-G@%@t4 zZify%esd{8`b!yWoIFS!)kLKa9qA@b_Tn{N{Ym@RUni3*Pi z*Oe%BD`usgrpcG-A5I&c%QB(>v%&UL3NH6Iw?yW13TrdLxd&{Xi z1Z14Bavf_KCLDG^j2bX4Ne#F;p}?j4qutMj$D2B&Zim-&)t^JF*RMb`(3L2N?VgA9 zp%WA6D;KF@3k&Ek^VBfc`O4HhnOVblL8e^86V&iPD(zzk?PIVS?i!#>uf$D{iS%#k zb13y`_wVNZCuldnLJs9*1ZA9dWBNP&yu=<)=cjZ;_V?v1xqgNDi=FR@;JYwG>^|U1 zajO)@mK4U86xveCl>W{AkGI?J(BWq=>i>Y5;)K`vC+!l(*@fY8w%OGq|1KF{Ih1e> zaWlsERYMj6skoRm1Nj|E>M^dzzD~6AKg4<7vbFWlUo18OFRcY|4-h zLpxLF(oeRs6M7rtJ|-~{mmaGaqsUL{G`C8fV)sQU7jaO=Rx`VGjSWBk9%BQhD-Oa@ zC#lp)Ds&-^>Y?cgYUH%L)JWIus{3q1qSW>N7}6djeX}2ZGl{;Ls0Q7fT&-!bFrG1h zaey(v_+j26e}l;1p!v2R>d?curTyss>el_Wuh5P$$*F_ITTyR_DWDDny2i$Lh+95aM;2Ttu*(=%LpIGl%Y{gmgvglZ>USHCFLZ%Vv)(e0)u>`AZ3pI2%J zM%s$N{zKwvgRC_e2Zqca*x|GWhenGIDD_9oqc)99AB$K=F#kGzOyb;gkn!mSrCxPt zdNO1E%?Yi2_s2EIR>u@Z7eu8CO}l8(HNOu%GeM1;_KoOquI16awJGl~^7|$2_6My> zJ&keN?TO~TEB~O>Z!yl?XWDWJZTV}xw&fPatuIS=`}<10k8#pVm~)T#81>lyP;k5VVO8qHdferUe&1l`l!_)F}g66srs z^UeCuH8N3+4D?qcOOol+{nW^=G2dS6bQ?cfSp%IYudR~Tp;Hso=s>A!bV-S8^t58v zXxGz7)@6QM zrV8#-&5pb~Ulw+oqq_XqUN!iSe7vE{f8^s09sak;$B%SHii0+};JeN-{GmK{)Qi=G zm<6T6AS@^flr2`*@)gOgg?nc>xN3`{{{b*X*tc{w}+L*u_QVfw@&R z3t%)y6x>0Nv!l^KXP`BFU4aekD>Pi!;#1xt_TfT*hog?g9rEU?5EC__%Kb0~_J{PX8 zE>)T0I;X0#wyL6ZPN1g3#8RU!)%L-f8ki>83 zj#*S$rkg}b&Z=TWzX=Zkh*YWjrJN^pj*8B$%`ROQT(P3Grl6*@7GkJVV&(@bE-t5% ziYgXW!nb0-Gg9pGs;aIGR?mf1E(wrnVG5;+%bcQWO89(N@`42punm8KtTHlJ;YI8{#E8#scxLDh2n=VTL+@7t?@rvs7y&4dY@6qz+O86{UfmROHZWK}9L@ z{F9^e=HwSu(~4eHm z>RPTqEG#FTT1inb^=*565sSsj7oAsCRFYS|tcEKOl=?N@2IiLO_3<~_LlMN!&ee&RkDtBlgoV z^39a1zd26P-%M*d%zWE^femGLk@zpcNZKrZb-0y4FNUc}4acy+)cKcki2pi_M`QpfRX$lAEPCLe`0^%0hIjx93$!7jS+tjW28*aVZ{9vjJT&l6rqn8q07Ja zmwdvXN!NSA-@i6r|F>d4vGASA!HI>x{%_^*U!Tqin}9t_pRfsd|MhwMH>B{tyh#+~ znDv({Dn<_=`)vOY;s5zN-?{T7^`|?nJ2~j=@e9X)?HxMAMNB9cz4rCjyz27Tu6S)q z58sT(FC2Qa^%JGexYmS3RaWPm2w#5t-buC%vurrih8Z@TX2WzFrrFSI!&Do(ZFsbg zq4Rq-Y_;JVHauj*7j3xThR@ir#fH0W*lfecY`D#a57=<44Y%0vHXGh(!v-5V@vpJJ z12(L%VWAC|*wAmo3>&7~@N^q`ZRob)(O6UNzD)S82s(Gz_LdD>ZFtCr`)$}_!)6<9 zwc%zPZnEJj8y4EIz=jz%Ot)d04ZSu@wPCUi-8NJ67^?HGPnht$A)*?=`K|O{LVnuoY>z2TssI^0Ps5CKFk~7 z&j6E9R9ctjQiFiYFk8mDR0%L`2)ujz2%N`-=uO}Sz@=>5mx2pCG*YPtzy-dIkvNr? z^BzpW7?<(_zrZX6SED%3!bn;HVC-n(#NG|e!PJqi==^LH96vV#Cyp_AI&kh-(!#$V z*ou*~1b%OvDeq<=dcbs8fp=rX&lX_9cw?UkoMq!J!23@{R~d0W0PMtkB>6c_snalu z{G1LfJ{=x`&;*z;k>Y_T0#C&hh#%nBXaq~ZmjZWUq%6CE?_wkm9|6xzM=lThEZ{dW zLgzKWUt`42R^Z4plzNPp8@<4DFcNWNV zux2J@!A}4;->+am1XP&M*H9i5q}Ku zo3qhD1il7%6GrmC3HTbDjxy{;R_WCo@+mlQyB`@O@W+4y&nHgsrNA{92`lh+8yEOC zM)IaEpqerJ@t+R#V-A5A058J40bU3!!nA^y0H^06j|-jwtipT*UJZ=TC;!x4B9Lo1 zDj+X#0x!l$9+m+AhLL*z2v`SmOz0`F`cmq0Jn;ZeTS`9#KOOiOW+Ax1GcKp!flmVt zDB_F}96fnzCPw0~SfPi2)u3u>axM>fUYuQ9|L?9lY#vkz?5=hp9-90<9=Ys#%~1v4wH@lX5c3np~L6E zd#*6}y}-;0+8cfXz#n2H4=uoPRkSzoG~ksO$$tQNH%9zy0bT<$@m}yXz)vwP;GYAp zt2KBXFg9RtH*gb1>Pz6+LFyO(Gl36cWc=I)jJe7#FR%mSK9xAd?rPc!xWKqorXIb( zKC7uC?A^dTjFeH}6cji}|C$C|^G(WvAAvu_NdLMW*ol#{h`iJYjFiy}T#MO^|E<7d zn62PyEn4NTC7csuorkQM#|U%Z2AS?*lz+pd6%J23o!p~L)!x2w=fd_2H-x7ghel;ddJ2E zKJZK9U*J2xGGnR0`|mYl<^#ZA{Tf=4*1f>ZzcF))z(W|RFM-LwHMqcCm{$B3Y^7Y7 z_rPxf&fEt7cmiz(*l#=I2zWAZHb&~S8u&a$^0{B|M`<(o*$?dVn2FyDy!CNTeX-vR z{1Zm{y9J#5gu%0b7N!nA0`J=a9~}Gv;Q2eD8+ab@SGy=L_`Sf>c2j=vEMQI>x7rku!F9D8!#o%ec zGK}~an0d&w!A)nZ<0X~Kidx0O@_)*|RpHd&#F9hzx$e8d9Fzz$z2zzv)s?#tM zR_^J@y`#@*O9JJdkKh93uFO`(B7t%bM(hRdwsE-&Blk_jUZC775&r^*es1gqiVVK^ z5h(W^1Q#fG8w3|9_YedZ_%j=qy9jcRK4*h{2a#nJvb@yloP3GDZuz`pea_8lj%S3(5)7nyGI3GBTmuut#BUii0J*caT% z*bRKgB%m^W!5Bk+obSTB7)#w<-|pWs#!(55d-VgjkL&tQeT{D_*>P`v7yrcVe5d`D zZ_4C+Z{picB|G1@{f%)UBK8WypnY7|*zw*ytyUb!2MICckL$7Q+4ac)q+(wG`ecWC0}kY)#sXAF z`>!r*?^jwuUl)InzsA#kK-cASz>uW;KR(n9w;u!Pu<09@JD_fnpa$+ zAG1FAdv-;!=*OD>Y~oDmW7gNdy>P7bv2I`E#>Uy+d`H@)FI9=hu9OqiQUg-qjymOP z`0RqLMdLappR=Ab9NVcZr{KP%Di`Ex$TgAcB6|qs+zr`+d^0)k)TtBRql`D#4jG~z zfBbQco00Lwix;b`tSq%@(QqrGDctWnV5;OC?(?f?2&5Ie($%fK8K0I-d z$Y!g|dd4en#89hBk<7f!L)qTz_~E}IT+4;4S96t?;wO}v<>4W2H9bUCb7asC)>WQO z9oA>ATgoT$C{XhWhUo^WMT-{7$Hxcn>1e0?{ry!?5Z)Uc7N&VOc<^8~Y}hdM&_fTY zM;>`Z&3del8Z%~$8aHm7ii?X=NlADgE$qk4nKM=T;ipPt`qu_l(5+p8(%| zG1i^AIClg1F-7nNq@H>f@GAhH1NdElKMeR&PVg-O9~cRLF#&$!V)%!-@CyOIr%0(o zfIkNKF9H8G;LifS5b#%=;C)+SehVty!{AyvcOlj~Sbr701tmOOPsy?NO1>DZUQ1N6I}L5FS91E$ zHF(Txk<|fzJK$>pzBb@te~RD?iREr3z1k}oIatZ#iAr8fQ?g~flB0*N!K*rWe@X+K zNooq8$p>oNMdd^Ci|~$TsrNAU-V&4yeo9H=3MFY9l&s&U&)eGd53fG;Y8e*kX>>5mp-(ZbVc;bpY27cG2+7K-YL`mw#J zOM^vSNfdQ8P1H~8Mg4L}%HZzgT>(!H+za^o0N)hwEdl=k;Cs~*HN3s3#KEE#B%-Y}QF-e{9Y1spzPxF$ zmL}($!NI+QdIyE*TLW5qw`lI^*|Kk0g`nQyVPPR5;lTj`K_S*Q-d~$##<97l1xSXKwQs%mp8ECs`|AdLG?h*99QcP2J}4Z|@2TIU zzXP`ct%(BQtpPz11H;2Z!>x_jKtuNi4gPZHop&}KKpgp;FaM7~FV;roDp<(|J`WC! z2n!F72#xS4R{_txTI=?EM}&ljMubH4xxdl9jxNxHwUu|90id7l2kR~j*Q`C=fda3< zKiz)&9uZ)1L}++~CPL$A_z(Q8A?*W+LU=@kwNalw_3PIM5oOP(;32SEpTQct`}e+{Z&x*`$v{JOa801$C%aw??}FYlJl-EHt7NOPG+- z6c*g6cd&1Dm)Zjz56G*q5SS~+b89zWw_3NmxYX+h42fbycmM?H+Vh~Uo!fP+Rn7J8 zFgy(I4O#BgDLDArbE~y?(4Zc5YS!q29)hiGJuKu}|JGp2-Jl+K-BvS@&w~RXuHgn8 z{3CxLV1akkt24+N91+k1vR3vO&rRy*R!t>rfOD}6IkhzZ8GkMXZB)!s znJ<^B0xI}(H}+GEKlk8+4{Cp8R&?Jo-{X~Oz0~~JP_-l}SZ$gUs&bdjQeF4Kr+}U7 z_lc-s@EzzgOhfs?3ooeU%a^N_D_5%Y^mMgm%^K}1Y}~j}`-5-1@rI(W@X@YU)N=S6 zx$qVC?%k_C{P08V8=N{>piZ7VsZO0brOux}ufF^4JN4rah1xf`eEG8a_19lj+Er2O z;VT^a#mUb4HpN8O6%!rwa`9+Pbki}>Ey6^%R@IYDs=e$~gJqvelp`ulK3D7IH0JMX z^NjMvgc#`#cucm79{_w8zy|_89PlFmp9uJ;0lyOP8vy?v;0wy;ng9AJVBdfJl>d`{ zN+VU88Z~MJCBi;tL;h{#-on?{w>3Xm8Z~ln)U>sSTb(-h!yj(w>D{7*R}0^IZgpGT zh3iI5n|XPmZap^-Umsr|)!4JOw{Mf$zV%R{&Ruui-?(WDZ{Is=d*AQ4VX=6(_H}i= z(;G0Y?yhrJBliZaeeZB}tzD}|jXPV_t=p*j?TuPDxx=+KZ}_@-+*{M7rYGw9`ZlRm zgYEyt{kHnJx}#a`TD5$z4rtoqzG{u}6d+A-jsATa-{aNH$Jf`#3;3h|);>PXeSDhw zX!;r>S&*7G)t4%zF81PUq9S}{on25?mU!RPVST_U55xvhz&%%wBD*LH{{E?S8=&E_ z>#r}sYu9BBlV~; zIF671kwpHmU94`Zl*n5*WQxCK)v8s0!@RS-u(0r(@4x^4Tg*KtFI>2A8fC$yOP30< zEcrQN%Cr}XaKyCd4+I5kFYfLsrmxNux+J2F3$$9(n|t}A=x^*VpzRwu{ey2>**0FA98_v}Vnkbp{U?o;!C=u%}zb=luM9`SjCIHJ%tBjXTHY#EBE~ z*=L{WYtm#gd>;K7GI!~RAATr?-2H+!&;0!J&+_AsKVJOkqmN$y`s=R?(AQ6d0iFMX zzI6r;3kmy2@rOSp=&LLff0M~qlQ||P6MyoGrTNTjW@#V3A&%4u=&&x2962J))D4aYOX>%8hcNHI z|GuVyV+j2hjsy1UxrJMnaQzGJm+(1sxC3aYs{S^-a^;F(8q)Ib=jYdwa?H#zz`mJm z-@aWi<^rEt>oCWFV}gA(or(LtefxyEa_rbK{h2h-22kFpCmbW6ZFh-0xL+jew8-TvSB^kesQ*<-8vmU;ccwLO-n=t>_=T{Sg7MHa(B^Oq z$XC+Cu^{gJ%<=#7%P)22XY!o68GVwRrjD;z0MNg;)l$XDKDbn{Cz7z5h z_)i)z23_74=>QtyKS8{s1pD2GMB44tVuhW>Dy4?lC#5Ve=-9ENCuCtB>A*N>dJG*b z$xF%+`Cl0w~lrzdbb;Fd@3#K7oi3|h{ z;gJ76;5TXTKPb}egHjsWK^L%3F5Y>%I_+pxlExplI1PLJoiPpzsb{n;mC-?YcODZX zS1ieYKIgnZSlSuqH0%^~lr(%H5(XMVK|}5Z=Ni}j`~#jWyACl8fBNYs!8}tglLnIw z9hHrVp~abwUw-*T4!yooUY-#y%Mt_Rg^7V0v4_7A8Tz%z;1ePdq~TMCK0{`D8hxfs zf z4s4QFruLM~$^PfHiX+;{qf6MD4gJ7qSKCBFX*n2Ji z(6xp1hp2Og4nqsafb)U#m>61E5`Wss&9j3f=ZPMY1sYxk4e66g@lP%kdGtJJI3w~m z&_I2rO$vuiGWtv!j6RbFqtCQS-rF_)I7w74HKd+#eu1A=mPv!j73na#;!FoWlLn@( zDcxkljP8>2cn^7X8fci}FPDqX$tO@}(qIJ*h_T7vob;JCiTWG_U7$_!gH7W6Y;2NO zo=CG&{43fejX(VR1)V#0_Jofzk95#3vZTzA4*EPSNel0Bt~GucpK-pW&%pFXYB$+3 ztDCF`4cVY!9cb9GbfR1;gz!`$odun77!yCv&!EBh7+yO|fy;3p_Mi5`$ba|l-CJ@j zOs2jPZ{kMW4K1|&wD(-s&~9?B;@rlxbB>?94jMMk>Mpr6dWan~RMh8x!zQK01<8W( zy=8uEu*@A3EGdtL$a9k)mM=d!D5SyJ$I$u=o5WNZ{;>C2{(;Xz;!eC+5+~wKeITFB zn9#;M`^WT$NF(L{t@*v=P0+9nG;Ep)8lVf*XVO4@rcGK3yGj}slZJ7<<>|4YAtpp- zJr=5IAfEIwI6oU7qci3=q~FOuZ3gFH`Vq|Q)~yqp%_j6qO*Z4f@ zU0|vVS#uA26?Nh3{}tC7|2A#fbivV{c>GlRdHB(K95OO8WYC~Ng0n^PkAM6_5L1%p zpMPHC!}UG+O&T~CaGs!CF>?(=8fZ@`hnx$^qrK0C$l+Ir{}tK4X38}m1G+#TgZfOH zv}{@g(ZA{X3wwXhAQU>A@&j2MKZ!eW zpugmtNrTCT4wh_>nKEVCrfvOT>nBN*>0??2!yrOcZ*?;_49$(%WJE zE43_<2I>X(eTWb&K*3SxU!wv7^*eM8svrj2U_yNCWLE_LgP%@ZtJC z$AC1LOd8C(mupJ;*pz$X$&xZe+KhbhK7A_s+^{A8#NJaEoHJa+HN>spPq}BNEOEb? zG!ZxMIpge|*5BaZU!cM6OEG_7ik3KnTDSJe)^;e)G*YH4Wqs_YI*Rnue&TC>bzdfR-)9xJLj!oq=t81assJ;Jyd3w51vX1KPdg{#W-?)DXK0I$ z*X#c%?xa!UZ~TAodmd>pcG1vcXkbZx(>7u5*6Rey6z5uJ{t{PS6Mv44@gW%3q1;oJ z$aCrtY{nAcaVxl&;qNT}v=PqZQQ4S~F7C09963^OE?3L9;kk3kdXy!~I`4B1AnqnU zf;H00KY_c(pM9A1FXo^9ux9*%a$#&Y}qm`&*Znsq?@us z-J##aYsw7U<6Hon`3hdaaI1VL?o4|B!FgUJ{w9+KlW#O8qzPxD^?XGcBMfOHzLc#z z*iO=7aEE`o_7>&66zgk$_5Kg^ORs-1f6pT=H*|&4Z8ocGUH4^L-Nz?f5J|b?f;Ml&YkpMX#Xe&oR2tnlE++glJ^`3 z`T}Mgcukv6TT45JHHD6Afad=+?xaJ@zq4#qlyh@!^wzngtn-?6I2M$7@|iSJ)*(l~ z!ACfQvEsbSGZuejZX$j+OLwCJ&mjE2%9 z2co%36Z>+2u$UTqdV%`-@_cDTwv-`?xg5#=T(1 z6gnWbGZK5lAOEOPx)BbfwQ-FaHM(MLmk6CMragntc^UThEarmmV3&@=KhMBE**N&X zA*hcxu_#aY8--&K<6xYOd!d2Yzh%su@#3QwMe?yLhwmdXeUJLrOHE+IGtp-;?I&#{ z*Gt5K*~Bm$KL2m9s~2H&kHBue!G;+#WxSDbF2+~5C(iiLN0&qng7zxJdOc{Tv9Az? zy{BQsfxZ*ho}3?P*Etu_R@0ZIpTcMS%rpYAD#kn+Yh#Ru=NA~GVtj{jf5zCDu17rX zdvFbaHE2B63*$Kda$e&)m;KU@CQlsnYu~A~#nQiwmpzQVTgLksE8A4${It@~3}QLU zgYKW}LHY>H#DSUiotZr0{B_~&52M&yT zGJdY*5jZf`#uyLfkufU9IvFQ?2s(na&oL$*oX4^65|8iSjpN+RY;d5@L7vdJ&Y2ag zV||Rza37J0eKRxm%J?y3e$Mj9vn-6!FxJNy6Xnt8O$~a*^iMy?#1}cQ(oZw~o56(; z+*jsaU?%o68S}+=>0~x^%ozvDn5G=fVCFPl>|5!Z2q%*f-^z zB@^RqjFB*2$T-!O7ZYw8Gd%aRNKye}p1^_Ud8iYN*)kdW=~qmjK0Q7qC1o6aP-cS% z_f5zPCho5@*2EYGV`YppF}}e#8DmV0Z7@d0_|lBgrTK+9u|gcQJRQm!togkM&_!S|^M=`hyQh zW#doZ3~`7keD87?Z2{N&^v_8*aUl;_9?p!_aYM$d7`tW6kg?}gj(8z;g7Fc?3R4lI zGCW{s&NiB{Tck4ir*7f9z45UBow!`s2idJm9YVv9y6x*kq!S&kn^YDoLrN&a%||;t5-+t_f97r zh+|G1HEPtm`2MzxA3t921LKUO-n%esAM%|1Apg0(qb!gg#J^%Hz{-s^QB=X%Cv7+Zp$B{=u3={D;x;=xRQ5RZyuL;N^z(ROfMisri@)4#h>^57a2 z{>M4S5*e4k_e_QRuf!oSF;VlK_JH#s+cq-5zGxSWu40}jL0o1GWH}i=65cYSc;@M5 zYbp=&3cO!DcI?=97~|m{J-+ZS91F(RFfZ$V=ns(Z?4OxF8GSTUVy^lb{Com!twOxw z0{Z4s;ATn7A9avz(YGVNxtB{B zcDYulO49b1_6O(a$FaQv?8$S^r_Et(0q-o(F=pxo@na$%%pNcOWyVzKw}XZi=(MVR z6F=R*k!SLinRqa>Kh8&ZM}oEuJgZ9DDRUez@|twhCS&hq?H}x0_s@P{Yqb5Z3=iW2 z<2wg}?>p+fV)}*LbD}){iN1CJq}R;9lqJ&3HkoPjsB_e9(n%TP`5m6U!1n^QeYi!s z**B91>95FlXZ~{xm}z@y`#8>cCj{m10`|k6K^xpZxz)t)nz-F!rheVbzFilu5)XW5 z*QMk~Xo_HyrI)zj6J@^()s3T&uLhT4^cpVyu;Ga^g<;XTPt`3e!H$ zMXbS=1826uwK&&a+>7A4kLyl9tUI|!O`nQ*({3?w4Z}6m#(yUY+i*_jVPd(b!+iv< z*~mYR6XziMK}_493f2A=*B@MaaP321m+KAtif4pva2?(ccyRpi?in5DrVS$>PV7yW zEvf!`JxSl4emmCsoxzTT)U|^cfMx)i{=v7sG#D8GjD$&eeYZ zOsstziNtOu|1d9TyTzCs&kqpR$lUr_z2w}9BbuLFLp>R*`@dx5hq6aoPrJjh#CO*< zPid<;mS674kPUPC>hs(yr}dZpZ@j|pHye0-cSZYZv|p4P+HLw=91q%4XI%K1bGd9wR@ZM}!@DfqO0W3-wcGHFbzJq^*Q()J z=@s9-Rvm9N;*~|ed98+{CazHDc1KN%e(PFIyjzX#-Y_*pS@Aa%?_n8&x5o@p192UO zzkTqT>CNhe@C{w`KN=){Vi~}PNY(KVXq8Jb@FHE%-X#25R;-FwW6)YGeo-qLEyt@E zH4(LY>pJa}AGS-oA$P)iXn?#5hdbh;f>9?9Z+D48{pr9a3Rls(k0EG@PuQ9T@2`nc zlTl|h-W?Z>-YjaUO4grP`S18@t4mqmA-JE6n#3sqxW%H6_$sv-iudD019CE;qJSs+ zX6k@n`nuNsFx_vmQ@ic)rgi3ax+K53IqV7;@?ny$ACDF%I8itW%YaU(AFcbud$CnB z)E|KBF}fx>lK`HOiZP&i659OzJqw)aV0^LCf>EeCzx*_AgB)#hAD>YQqQF5#L4I-`mxBQ*eUq6)G^V?We=SnhfV`1f1h|j^pxlcmI?gp?-`XG z7C&X;_~;~0%jDRg(WCJ*y8fOqQ4^A*J$v=^Eo-|xa9R6KHGbE7Pv3I5_Vg_y8sI&B z4L^HD21N#igoF+3JA61kaHRO9>|+@x@cT|h8LpXbnUR^pGnE_OF^&8CRv%k^W_9su z*L3%E?{vTPe(A&0$EHt9pP#-YeO>yt^nK~a($Az9r@LmjXYiLBjsixlc3YkL>f)>= zS*x?wW#wjV%i5K-FY92|v8)qWXR?a2inEl>)#he%w^?l7wstl@TcE9D) zo!u_mFFP>1U-q`_W7);o?m2!r({dK)EXi4&vo0q$XIBnriKLd}RVNwKGEy_t?Wre9`1&BsSG$7UvEPRmTqBxC-Y z{>y>?T^wlEG`Rc7$mx^DPK+Pfv2E9p3HoE(=xNcl@2VZyzgqQsG``=u%pB@jbJ3Jf zaK+5^rqvo36&sH?p(RXjW@*#9jRn7~jvwvrZkaqOri~x()Q*iyn3y!lk`!$|B~MST z9g{RM&Js6y5`L;YzO8lA#EBD<+s4H{)^SP)i=#e%{5@&9HGx0cUOP6%VztKON4l+6 zi@(3c%k=8i9fsXvL4$3hlEzFK(e4q8KRRlgJb9FNl9zXzNsETeSlHF1OvI-@$>GOIN}H%^Lhk)t;8Y3|&S#ex-$8 KSvS_w75)e1uk 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 OEp*Jfp+YrX;eP-wur;Fq literal 0 HcmV?d00001 diff --git a/libs/common/bin/mutagen-inspect.exe b/libs/common/bin/mutagen-inspect.exe new file mode 100644 index 0000000000000000000000000000000000000000..612fb40cff6e0f767555d6a85b3e69cd9608960d GIT binary patch literal 108405 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 OTIim2LzP;(!v6s6o;SMy literal 0 HcmV?d00001 diff --git a/libs/common/bin/mutagen-pony.exe b/libs/common/bin/mutagen-pony.exe new file mode 100644 index 0000000000000000000000000000000000000000..e6d375a39f62a618c49b15e760afc71eecf642b1 GIT binary patch literal 108402 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 literal 0 HcmV?d00001 diff --git a/libs/common/bin/srt.exe b/libs/common/bin/srt.exe new file mode 100644 index 0000000000000000000000000000000000000000..eb48088fc38255dd63119303915886dbdfcd52ae GIT binary patch literal 108375 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 literal 0 HcmV?d00001 diff --git a/libs/common/bin/subliminal.exe b/libs/common/bin/subliminal.exe new file mode 100644 index 0000000000000000000000000000000000000000..2c8e7fab1f6ca7abf4eb07e64d2ce267a3f78542 GIT binary patch literal 108387 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 nGwx$e0hxt_U} zT;JRdxdFMsx&3pu=N`{3&GpO+$eW(GByVZnn!NRS`FXqZKua{W9Irabnx2)OwIFLr z*3zssS?ja%v$kjL&f1rCDC@Z9TWOZcCht76E!n==9kK(m`)fWvlszsxDSLW$dNvvJ z-}GM&{O{sGi{_y{Mn%tjY`rV+s96it$$1X=mE8A-=^_B>o#kchx`6k z2XV#B^`_OD5EGw>H=!lXXklsBww(pPtd1Y;?qQiSIj&s*@6?Y^NSc%~*^&}#KBdk` ztsj>%DZ!FDW%R`Og!sgmiS5TsjJG%|R@|*0A7`;z6Y=BRR;wj2(Bfly^XUPDZn2=D z_Ax1Ar;hCKpd}C;v_#nH$v?*^Sc6$6| v-->>H+q41MZa3VfU%i3%kXJ*v<_P|zYX{39h90AxKh8pDtUD?=zD4vu5+N>$ literal 0 HcmV?d00001 diff --git a/libs/common/bin/unidecode.exe b/libs/common/bin/unidecode.exe new file mode 100644 index 0000000000000000000000000000000000000000..2dfd0da4f14613dc6032768bb3687530470aab20 GIT binary patch literal 108375 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 cmd_param.nargs: - break - if start_of_option(arg_str): - last_option = arg_str - - return True if last_option and last_option in cmd_param.opts else False - - -def is_incomplete_argument(current_params, cmd_param): - """ - :param current_params: the current params and values for this argument as already entered - :param cmd_param: the current command parameter - :return: whether or not the last argument is incomplete and corresponds to this cmd_param. In - other words whether or not the this cmd_param argument can still accept values - """ - if not isinstance(cmd_param, Argument): - return False - current_param_values = current_params[cmd_param.name] - if current_param_values is None: - return True - if cmd_param.nargs == -1: - return True - if isinstance(current_param_values, abc.Iterable) \ - and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs: - return True - return False - - -def get_user_autocompletions(ctx, args, incomplete, cmd_param): - """ - :param ctx: context associated with the parsed command - :param args: full list of args - :param incomplete: the incomplete text to autocomplete - :param cmd_param: command definition - :return: all the possible user-specified completions for the param - """ - results = [] - if isinstance(cmd_param.type, Choice): - # Choices don't support descriptions. - results = [(c, None) - for c in cmd_param.type.choices if str(c).startswith(incomplete)] - elif cmd_param.autocompletion is not None: - dynamic_completions = cmd_param.autocompletion(ctx=ctx, - args=args, - incomplete=incomplete) - results = [c if isinstance(c, tuple) else (c, None) - for c in dynamic_completions] - return results - - -def get_visible_commands_starting_with(ctx, starts_with): - """ - :param ctx: context associated with the parsed command - :starts_with: string that visible commands must start with. - :return: all visible (not hidden) commands that start with starts_with. - """ - for c in ctx.command.list_commands(ctx): - if c.startswith(starts_with): - command = ctx.command.get_command(ctx, c) - if not command.hidden: - yield command - - -def add_subcommand_completions(ctx, incomplete, completions_out): - # Add subcommand completions. - if isinstance(ctx.command, MultiCommand): - completions_out.extend( - [(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)]) - - # Walk up the context list and add any other completion possibilities from chained commands - while ctx.parent is not None: - ctx = ctx.parent - if isinstance(ctx.command, MultiCommand) and ctx.command.chain: - remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete) - if c.name not in ctx.protected_args] - completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands]) - - -def get_choices(cli, prog_name, args, incomplete): - """ - :param cli: command definition - :param prog_name: the program that is running - :param args: full list of args - :param incomplete: the incomplete text to autocomplete - :return: all the possible completions for the incomplete - """ - all_args = copy.deepcopy(args) - - ctx = resolve_ctx(cli, prog_name, args) - if ctx is None: - return [] - - # In newer versions of bash long opts with '='s are partitioned, but it's easier to parse - # without the '=' - if start_of_option(incomplete) and WORDBREAK in incomplete: - partition_incomplete = incomplete.partition(WORDBREAK) - all_args.append(partition_incomplete[0]) - incomplete = partition_incomplete[2] - elif incomplete == WORDBREAK: - incomplete = '' - - completions = [] - if start_of_option(incomplete): - # completions for partial options - for param in ctx.command.params: - if isinstance(param, Option) and not param.hidden: - param_opts = [param_opt for param_opt in param.opts + - param.secondary_opts if param_opt not in all_args or param.multiple] - completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)]) - return completions - # completion for option values from user supplied values - for param in ctx.command.params: - if is_incomplete_option(all_args, param): - return get_user_autocompletions(ctx, all_args, incomplete, param) - # completion for argument values from user supplied values - for param in ctx.command.params: - if is_incomplete_argument(ctx.params, param): - return get_user_autocompletions(ctx, all_args, incomplete, param) - - add_subcommand_completions(ctx, incomplete, completions) - # Sort before returning so that proper ordering can be enforced in custom types. - return sorted(completions) - - -def do_complete(cli, prog_name, include_descriptions): - cwords = split_arg_string(os.environ['COMP_WORDS']) - cword = int(os.environ['COMP_CWORD']) - args = cwords[1:cword] - try: - incomplete = cwords[cword] - except IndexError: - incomplete = '' - - for item in get_choices(cli, prog_name, args, incomplete): - echo(item[0]) - if include_descriptions: - # ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present. - echo(item[1] if item[1] else '_') - - return True - - -def bashcomplete(cli, prog_name, complete_var, complete_instr): - if complete_instr.startswith('source'): - shell = 'zsh' if complete_instr == 'source_zsh' else 'bash' - echo(get_completion_script(prog_name, complete_var, shell)) - return True - elif complete_instr == 'complete' or complete_instr == 'complete_zsh': - return do_complete(cli, prog_name, complete_instr == 'complete_zsh') - return False diff --git a/libs/common/click/_compat.py b/libs/common/click/_compat.py index 937e2301..766d286b 100644 --- a/libs/common/click/_compat.py +++ b/libs/common/click/_compat.py @@ -1,92 +1,90 @@ -import re +import codecs import io import os +import re import sys -import codecs +import typing as t from weakref import WeakKeyDictionary - -PY2 = sys.version_info[0] == 2 -CYGWIN = sys.platform.startswith('cygwin') +CYGWIN = sys.platform.startswith("cygwin") +MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version) # Determine local App Engine environment, per Google's own suggestion -APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) -WIN = sys.platform.startswith('win') and not APP_ENGINE -DEFAULT_COLUMNS = 80 +APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get( + "SERVER_SOFTWARE", "" +) +WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2 +auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") -_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])') - - -def get_filesystem_encoding(): +def get_filesystem_encoding() -> str: return sys.getfilesystemencoding() or sys.getdefaultencoding() -def _make_text_stream(stream, encoding, errors, - force_readable=False, force_writable=False): +def _make_text_stream( + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: if encoding is None: encoding = get_best_encoding(stream) if errors is None: - errors = 'replace' - return _NonClosingTextIOWrapper(stream, encoding, errors, - line_buffering=True, - force_readable=force_readable, - force_writable=force_writable) + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) -def is_ascii_encoding(encoding): +def is_ascii_encoding(encoding: str) -> bool: """Checks if a given encoding is ascii.""" try: - return codecs.lookup(encoding).name == 'ascii' + return codecs.lookup(encoding).name == "ascii" except LookupError: return False -def get_best_encoding(stream): +def get_best_encoding(stream: t.IO) -> str: """Returns the default stream encoding if not found.""" - rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding() + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() if is_ascii_encoding(rv): - return 'utf-8' + return "utf-8" return rv class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) - def __init__(self, stream, encoding, errors, - force_readable=False, force_writable=False, **extra): - self._stream = stream = _FixupStream(stream, force_readable, - force_writable) - io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) - - # The io module is a place where the Python 3 text behavior - # was forced upon Python 2, so we need to unbreak - # it to look like Python 2. - if PY2: - def write(self, x): - if isinstance(x, str) or is_bytes(x): - try: - self.flush() - except Exception: - pass - return self.buffer.write(str(x)) - return io.TextIOWrapper.write(self, x) - - def writelines(self, lines): - for line in lines: - self.write(line) - - def __del__(self): + def __del__(self) -> None: try: self.detach() except Exception: pass - def isatty(self): + def isatty(self) -> bool: # https://bitbucket.org/pypy/pypy/issue/1803 return self._stream.isatty() -class _FixupStream(object): +class _FixupStream: """The new io interface needs more from streams than streams traditionally implement. As such, this fix-up code is necessary in some circumstances. @@ -96,56 +94,58 @@ class _FixupStream(object): of jupyter notebook). """ - def __init__(self, stream, force_readable=False, force_writable=False): + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): self._stream = stream self._force_readable = force_readable self._force_writable = force_writable - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._stream, name) - def read1(self, size): - f = getattr(self._stream, 'read1', None) + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + if f is not None: - return f(size) - # We only dispatch to readline instead of read in Python 2 as we - # do not want cause problems with the different implementation - # of line buffering. - if PY2: - return self._stream.readline(size) + return t.cast(bytes, f(size)) + return self._stream.read(size) - def readable(self): + def readable(self) -> bool: if self._force_readable: return True - x = getattr(self._stream, 'readable', None) + x = getattr(self._stream, "readable", None) if x is not None: - return x() + return t.cast(bool, x()) try: self._stream.read(0) except Exception: return False return True - def writable(self): + def writable(self) -> bool: if self._force_writable: return True - x = getattr(self._stream, 'writable', None) + x = getattr(self._stream, "writable", None) if x is not None: - return x() + return t.cast(bool, x()) try: - self._stream.write('') + self._stream.write("") # type: ignore except Exception: try: - self._stream.write(b'') + self._stream.write(b"") except Exception: return False return True - def seekable(self): - x = getattr(self._stream, 'seekable', None) + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) if x is not None: - return x() + return t.cast(bool, x()) try: self._stream.seek(self._stream.tell()) except Exception: @@ -153,518 +153,442 @@ class _FixupStream(object): return True -if PY2: - text_type = unicode - bytes = str - raw_input = raw_input - string_types = (str, unicode) - int_types = (int, long) - iteritems = lambda x: x.iteritems() - range_type = xrange - - def is_bytes(x): - return isinstance(x, (buffer, bytearray)) - - _identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') - - # For Windows, we need to force stdout/stdin/stderr to binary if it's - # fetched for that. This obviously is not the most correct way to do - # it as it changes global state. Unfortunately, there does not seem to - # be a clear better way to do it as just reopening the file in binary - # mode does not change anything. - # - # An option would be to do what Python 3 does and to open the file as - # binary only, patch it back to the system, and then use a wrapper - # stream that converts newlines. It's not quite clear what's the - # correct option here. - # - # This code also lives in _winconsole for the fallback to the console - # emulation stream. - # - # There are also Windows environments where the `msvcrt` module is not - # available (which is why we use try-catch instead of the WIN variable - # here), such as the Google App Engine development server on Windows. In - # those cases there is just nothing we can do. - def set_binary_mode(f): - return f - +def _is_binary_reader(stream: t.IO, default: bool = False) -> bool: try: - import msvcrt - except ImportError: - pass - else: - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - msvcrt.setmode(fileno, os.O_BINARY) - return f + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + +def _is_binary_writer(stream: t.IO, default: bool = False) -> bool: try: - import fcntl - except ImportError: - pass - else: - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - flags = fcntl.fcntl(fileno, fcntl.F_GETFL) - fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) - return f - - def isidentifier(x): - return _identifier_re.search(x) is not None - - def get_binary_stdin(): - return set_binary_mode(sys.stdin) - - def get_binary_stdout(): - _wrap_std_stream('stdout') - return set_binary_mode(sys.stdout) - - def get_binary_stderr(): - _wrap_std_stream('stderr') - return set_binary_mode(sys.stderr) - - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdin, encoding, errors, - force_readable=True) - - def get_text_stdout(encoding=None, errors=None): - _wrap_std_stream('stdout') - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdout, encoding, errors, - force_writable=True) - - def get_text_stderr(encoding=None, errors=None): - _wrap_std_stream('stderr') - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stderr, encoding, errors, - force_writable=True) - - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), 'replace') - return value -else: - import io - text_type = str - raw_input = input - string_types = (str,) - int_types = (int,) - range_type = range - isidentifier = lambda x: x.isidentifier() - iteritems = lambda x: iter(x.items()) - - def is_bytes(x): - return isinstance(x, (bytes, memoryview, bytearray)) - - def _is_binary_reader(stream, default=False): + stream.write(b"") + except Exception: try: - return isinstance(stream.read(0), bytes) + stream.write("") + return False except Exception: - return default - # This happens in some cases where the stream was already - # closed. In this case, we assume the default. - - def _is_binary_writer(stream, default=False): - try: - stream.write(b'') - except Exception: - try: - stream.write('') - return False - except Exception: - pass - return default - return True - - def _find_binary_reader(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detaching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_reader(stream, False): - return stream - - buf = getattr(stream, 'buffer', None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_reader(buf, True): - return buf - - def _find_binary_writer(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detatching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_writer(stream, False): - return stream - - buf = getattr(stream, 'buffer', None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_writer(buf, True): - return buf - - def _stream_is_misconfigured(stream): - """A stream is misconfigured if its encoding is ASCII.""" - # If the stream does not have an encoding set, we assume it's set - # to ASCII. This appears to happen in certain unittest - # environments. It's not quite clear what the correct behavior is - # but this at least will force Click to recover somehow. - return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii') - - def _is_compatible_text_stream(stream, encoding, errors): - stream_encoding = getattr(stream, 'encoding', None) - stream_errors = getattr(stream, 'errors', None) - - # Perfect match. - if stream_encoding == encoding and stream_errors == errors: - return True - - # Otherwise, it's only a compatible stream if we did not ask for - # an encoding. - if encoding is None: - return stream_encoding is not None - - return False - - def _force_correct_text_reader(text_reader, encoding, errors, - force_readable=False): - if _is_binary_reader(text_reader, False): - binary_reader = text_reader - else: - # If there is no target encoding set, we need to verify that the - # reader is not actually misconfigured. - if encoding is None and not _stream_is_misconfigured(text_reader): - return text_reader - - if _is_compatible_text_stream(text_reader, encoding, errors): - return text_reader - - # If the reader has no encoding, we try to find the underlying - # binary reader for it. If that fails because the environment is - # misconfigured, we silently go with the same reader because this - # is too common to happen. In that case, mojibake is better than - # exceptions. - binary_reader = _find_binary_reader(text_reader) - if binary_reader is None: - return text_reader - - # At this point, we default the errors to replace instead of strict - # because nobody handles those errors anyways and at this point - # we're so fundamentally fucked that nothing can repair it. - if errors is None: - errors = 'replace' - return _make_text_stream(binary_reader, encoding, errors, - force_readable=force_readable) - - def _force_correct_text_writer(text_writer, encoding, errors, - force_writable=False): - if _is_binary_writer(text_writer, False): - binary_writer = text_writer - else: - # If there is no target encoding set, we need to verify that the - # writer is not actually misconfigured. - if encoding is None and not _stream_is_misconfigured(text_writer): - return text_writer - - if _is_compatible_text_stream(text_writer, encoding, errors): - return text_writer - - # If the writer has no encoding, we try to find the underlying - # binary writer for it. If that fails because the environment is - # misconfigured, we silently go with the same writer because this - # is too common to happen. In that case, mojibake is better than - # exceptions. - binary_writer = _find_binary_writer(text_writer) - if binary_writer is None: - return text_writer - - # At this point, we default the errors to replace instead of strict - # because nobody handles those errors anyways and at this point - # we're so fundamentally fucked that nothing can repair it. - if errors is None: - errors = 'replace' - return _make_text_stream(binary_writer, encoding, errors, - force_writable=force_writable) - - def get_binary_stdin(): - reader = _find_binary_reader(sys.stdin) - if reader is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stdin.') - return reader - - def get_binary_stdout(): - writer = _find_binary_writer(sys.stdout) - if writer is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stdout.') - return writer - - def get_binary_stderr(): - writer = _find_binary_writer(sys.stderr) - if writer is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stderr.') - return writer - - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_reader(sys.stdin, encoding, errors, - force_readable=True) - - def get_text_stdout(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer(sys.stdout, encoding, errors, - force_writable=True) - - def get_text_stderr(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer(sys.stderr, encoding, errors, - force_writable=True) - - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), 'replace') - else: - value = value.encode('utf-8', 'surrogateescape') \ - .decode('utf-8', 'replace') - return value + pass + return default + return True -def get_streerror(e, default=None): - if hasattr(e, 'strerror'): - msg = e.strerror +def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + is_binary: t.Callable[[t.IO, bool], bool], + find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) else: - if default is not None: - msg = default - else: - msg = str(e) - if isinstance(msg, bytes): - msg = msg.decode('utf-8', 'replace') - return msg + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) -def open_stream(filename, mode='r', encoding=None, errors='strict', - atomic=False): - # Standard streams first. These are simple because they don't need - # special handling for the atomic flag. It's entirely ignored. - if filename == '-': - if any(m in mode for m in ['w', 'a', 'x']): - if 'b' in mode: +def _force_correct_text_reader( + text_reader: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: t.Union[str, os.PathLike, int], + mode: str, + encoding: t.Optional[str], + errors: t.Optional[str], +) -> t.IO: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, +) -> t.Tuple[t.IO, bool]: + binary = "b" in mode + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: return get_binary_stdout(), False return get_text_stdout(encoding=encoding, errors=errors), False - if 'b' in mode: + if binary: return get_binary_stdin(), False return get_text_stdin(encoding=encoding, errors=errors), False # Non-atomic writes directly go out through the regular open functions. if not atomic: - if encoding is None: - return open(filename, mode), True - return io.open(filename, mode, encoding=encoding, errors=errors), True + return _wrap_io_open(filename, mode, encoding, errors), True # Some usability stuff for atomic writes - if 'a' in mode: + if "a" in mode: raise ValueError( - 'Appending to an existing file is not supported, because that ' - 'would involve an expensive `copy`-operation to a temporary ' - 'file. Open the file in normal `w`-mode and copy explicitly ' - 'if that\'s what you\'re after.' + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." ) - if 'x' in mode: - raise ValueError('Use the `overwrite`-parameter instead.') - if 'w' not in mode: - raise ValueError('Atomic writes only make sense with `w`-mode.') + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") # Atomic writes are more complicated. They work by opening a file # as a proxy in the same folder and then using the fdopen # functionality to wrap it in a Python file. Then we wrap it in an # atomic file that moves the file over on close. - import tempfile - fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename), - prefix='.__atomic-write') + import errno + import random - if encoding is not None: - f = io.open(fd, mode, encoding=encoding, errors=errors) - else: - f = os.fdopen(fd, mode) + try: + perm: t.Optional[int] = os.stat(filename).st_mode + except OSError: + perm = None - return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO, af), True -# Used in a destructor call, needs extra protection from interpreter cleanup. -if hasattr(os, 'replace'): - _replace = os.replace - _can_replace = True -else: - _replace = os.rename - _can_replace = not WIN - - -class _AtomicFile(object): - - def __init__(self, f, tmp_filename, real_filename): +class _AtomicFile: + def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None: self._f = f self._tmp_filename = tmp_filename self._real_filename = real_filename self.closed = False @property - def name(self): + def name(self) -> str: return self._real_filename - def close(self, delete=False): + def close(self, delete: bool = False) -> None: if self.closed: return self._f.close() - if not _can_replace: - try: - os.remove(self._real_filename) - except OSError: - pass - _replace(self._tmp_filename, self._real_filename) + os.replace(self._tmp_filename, self._real_filename) self.closed = True - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._f, name) - def __enter__(self): + def __enter__(self) -> "_AtomicFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.close(delete=exc_type is not None) - def __repr__(self): + def __repr__(self) -> str: return repr(self._f) -auto_wrap_for_ansi = None -colorama = None -get_winterm_size = None +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) -def strip_ansi(value): - return _ansi_re.sub('', value) +def _is_jupyter_kernel_output(stream: t.IO) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") -def should_strip_ansi(stream=None, color=None): +def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None +) -> bool: if color is None: if stream is None: stream = sys.stdin - return not isatty(stream) + return not isatty(stream) and not _is_jupyter_kernel_output(stream) return not color -# If we're on Windows, we provide transparent integration through -# colorama. This will make ANSI colors through the echo function -# work automatically. -if WIN: - # Windows has a smaller terminal - DEFAULT_COLUMNS = 79 +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream - from ._winconsole import _get_windows_console_stream, _wrap_std_stream - - def _get_argv_encoding(): + def _get_argv_encoding() -> str: import locale + return locale.getpreferredencoding() - if PY2: - def raw_input(prompt=''): - sys.stderr.flush() - if prompt: - stdout = _default_text_stdout() - stdout.write(prompt) - stdin = _default_text_stdin() - return stdin.readline().rstrip('\r\n') + _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi( + stream: t.TextIO, color: t.Optional[bool] = None + ) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached - try: import colorama - except ImportError: - pass - else: - _ansi_stream_wrappers = WeakKeyDictionary() - def auto_wrap_for_ansi(stream, color=None): - """This function wraps a stream so that calls through colorama - are issued to the win32 console API to recolor on demand. It - also ensures to reset the colors if a write call is interrupted - to not destroy the console afterwards. - """ + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s): try: - cached = _ansi_stream_wrappers.get(stream) - except Exception: - cached = None - if cached is not None: - return cached - strip = should_strip_ansi(stream, color) - ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) - rv = ansi_wrapper.stream - _write = rv.write + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise - def _safe_write(s): - try: - return _write(s) - except: - ansi_wrapper.reset_all() - raise + rv.write = _safe_write - rv.write = _safe_write - try: - _ansi_stream_wrappers[stream] = rv - except Exception: - pass - return rv + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv - def get_winterm_size(): - win = colorama.win32.GetConsoleScreenBufferInfo( - colorama.win32.STDOUT).srWindow - return win.Right - win.Left, win.Bottom - win.Top else: - def _get_argv_encoding(): - return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding() - _get_windows_console_stream = lambda *x: None - _wrap_std_stream = lambda *x: None + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] + ) -> t.Optional[t.TextIO]: + return None -def term_len(x): +def term_len(x: str) -> int: return len(strip_ansi(x)) -def isatty(stream): +def isatty(stream: t.IO) -> bool: try: return stream.isatty() except Exception: return False -def _make_cached_stream_func(src_func, wrapper_func): - cache = WeakKeyDictionary() - def func(): +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO] +) -> t.Callable[[], t.TextIO]: + cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO: stream = src_func() try: rv = cache.get(stream) @@ -674,30 +598,29 @@ def _make_cached_stream_func(src_func, wrapper_func): return rv rv = wrapper_func() try: - stream = src_func() # In case wrapper_func() modified the stream cache[stream] = rv except Exception: pass return rv + return func -_default_text_stdin = _make_cached_stream_func( - lambda: sys.stdin, get_text_stdin) -_default_text_stdout = _make_cached_stream_func( - lambda: sys.stdout, get_text_stdout) -_default_text_stderr = _make_cached_stream_func( - lambda: sys.stderr, get_text_stderr) +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) -binary_streams = { - 'stdin': get_binary_stdin, - 'stdout': get_binary_stdout, - 'stderr': get_binary_stderr, +binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, } -text_streams = { - 'stdin': get_text_stdin, - 'stdout': get_text_stdout, - 'stderr': get_text_stderr, +text_streams: t.Mapping[ + str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] +] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, } diff --git a/libs/common/click/_termui_impl.py b/libs/common/click/_termui_impl.py index 00a8e5ef..4b979bcc 100644 --- a/libs/common/click/_termui_impl.py +++ b/libs/common/click/_termui_impl.py @@ -1,62 +1,56 @@ -# -*- coding: utf-8 -*- """ -click._termui_impl -~~~~~~~~~~~~~~~~~~ - This module contains implementations for the termui module. To keep the import time of Click down, some infrequently used functionality is placed in this module and only imported as needed. - -:copyright: © 2014 by the Pallets team. -:license: BSD, see LICENSE.rst for more details. """ - +import contextlib +import math import os import sys import time -import math -import contextlib -from ._compat import _default_text_stdout, range_type, PY2, isatty, \ - open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \ - CYGWIN -from .utils import echo +import typing as t +from gettext import gettext as _ + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN from .exceptions import ClickException +from .utils import echo +V = t.TypeVar("V") -if os.name == 'nt': - BEFORE_BAR = '\r' - AFTER_BAR = '\n' +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" else: - BEFORE_BAR = '\r\033[?25l' - AFTER_BAR = '\033[?25h\n' + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" -def _length_hint(obj): - """Returns the length hint of an object.""" - try: - return len(obj) - except (AttributeError, TypeError): - try: - get_hint = type(obj).__length_hint__ - except AttributeError: - return None - try: - hint = get_hint(obj) - except TypeError: - return None - if hint is NotImplemented or \ - not isinstance(hint, int_types) or \ - hint < 0: - return None - return hint - - -class ProgressBar(object): - - def __init__(self, iterable, length=None, fill_char='#', empty_char=' ', - bar_template='%(bar)s', info_sep=' ', show_eta=True, - show_percent=None, show_pos=False, item_show_func=None, - label=None, file=None, color=None, width=30): +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: t.Optional[t.Iterable[V]], + length: t.Optional[int] = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + label: t.Optional[str] = None, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: self.fill_char = fill_char self.empty_char = empty_char self.bar_template = bar_template @@ -65,77 +59,87 @@ class ProgressBar(object): self.show_percent = show_percent self.show_pos = show_pos self.item_show_func = item_show_func - self.label = label or '' + self.label = label or "" if file is None: file = _default_text_stdout() self.file = file self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 self.width = width self.autowidth = width == 0 if length is None: - length = _length_hint(iterable) + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None if iterable is None: if length is None: - raise TypeError('iterable or length is required') - iterable = range_type(length) + raise TypeError("iterable or length is required") + iterable = t.cast(t.Iterable[V], range(length)) self.iter = iter(iterable) self.length = length - self.length_known = length is not None self.pos = 0 - self.avg = [] + self.avg: t.List[float] = [] self.start = self.last_eta = time.time() self.eta_known = False self.finished = False - self.max_width = None + self.max_width: t.Optional[int] = None self.entered = False - self.current_item = None + self.current_item: t.Optional[V] = None self.is_hidden = not isatty(self.file) - self._last_line = None - self.short_limit = 0.5 + self._last_line: t.Optional[str] = None - def __enter__(self): + def __enter__(self) -> "ProgressBar": self.entered = True self.render_progress() return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.render_finish() - def __iter__(self): + def __iter__(self) -> t.Iterator[V]: if not self.entered: - raise RuntimeError('You need to use progress bars in a with block.') + raise RuntimeError("You need to use progress bars in a with block.") self.render_progress() return self.generator() - def is_fast(self): - return time.time() - self.start <= self.short_limit + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) - def render_finish(self): - if self.is_hidden or self.is_fast(): + def render_finish(self) -> None: + if self.is_hidden: return self.file.write(AFTER_BAR) self.file.flush() @property - def pct(self): + def pct(self) -> float: if self.finished: return 1.0 - return min(self.pos / (float(self.length) or 1), 1.0) + return min(self.pos / (float(self.length or 1) or 1), 1.0) @property - def time_per_iteration(self): + def time_per_iteration(self) -> float: if not self.avg: return 0.0 return sum(self.avg) / float(len(self.avg)) @property - def eta(self): - if self.length_known and not self.finished: + def eta(self) -> float: + if self.length is not None and not self.finished: return self.time_per_iteration * (self.length - self.pos) return 0.0 - def format_eta(self): + def format_eta(self) -> str: if self.eta_known: t = int(self.eta) seconds = t % 60 @@ -145,41 +149,44 @@ class ProgressBar(object): hours = t % 24 t //= 24 if t > 0: - days = t - return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" else: - return '%02d:%02d:%02d' % (hours, minutes, seconds) - return '' + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" - def format_pos(self): + def format_pos(self) -> str: pos = str(self.pos) - if self.length_known: - pos += '/%s' % self.length + if self.length is not None: + pos += f"/{self.length}" return pos - def format_pct(self): - return ('% 4d%%' % int(self.pct * 100))[1:] + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] - def format_bar(self): - if self.length_known: + def format_bar(self) -> str: + if self.length is not None: bar_length = int(self.pct * self.width) bar = self.fill_char * bar_length bar += self.empty_char * (self.width - bar_length) elif self.finished: bar = self.fill_char * self.width else: - bar = list(self.empty_char * (self.width or 1)) + chars = list(self.empty_char * (self.width or 1)) if self.time_per_iteration != 0: - bar[int((math.cos(self.pos * self.time_per_iteration) - / 2.0 + 0.5) * self.width)] = self.fill_char - bar = ''.join(bar) + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) return bar - def format_progress_line(self): + def format_progress_line(self) -> str: show_percent = self.show_percent info_bits = [] - if self.length_known and show_percent is None: + if self.length is not None and show_percent is None: show_percent = not self.show_pos if self.show_pos: @@ -193,16 +200,25 @@ class ProgressBar(object): if item_info is not None: info_bits.append(item_info) - return (self.bar_template % { - 'label': self.label, - 'bar': self.format_bar(), - 'info': self.info_sep.join(info_bits) - }).rstrip() + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() - def render_progress(self): - from .termui import get_terminal_size + def render_progress(self) -> None: + import shutil if self.is_hidden: + # Only output the label as it changes if the output is not a + # TTY. Use file=stderr if you expect to be piping stdout. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + return buf = [] @@ -211,10 +227,10 @@ class ProgressBar(object): old_width = self.width self.width = 0 clutter_length = term_len(self.format_progress_line()) - new_width = max(0, get_terminal_size()[0] - clutter_length) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) if new_width < old_width: buf.append(BEFORE_BAR) - buf.append(' ' * self.max_width) + buf.append(" " * self.max_width) # type: ignore self.max_width = new_width self.width = new_width @@ -229,18 +245,18 @@ class ProgressBar(object): self.max_width = line_len buf.append(line) - buf.append(' ' * (clear_width - line_len)) - line = ''.join(buf) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) # Render the line only if it changed. - if line != self._last_line and not self.is_fast(): + if line != self._last_line: self._last_line = line echo(line, file=self.file, color=self.color, nl=False) self.file.flush() - def make_step(self, n_steps): + def make_step(self, n_steps: int) -> None: self.pos += n_steps - if self.length_known and self.pos >= self.length: + if self.length is not None and self.pos >= self.length: self.finished = True if (time.time() - self.last_eta) < 1.0: @@ -258,97 +274,134 @@ class ProgressBar(object): self.avg = self.avg[-6:] + [step] - self.eta_known = self.length_known + self.eta_known = self.length is not None - def update(self, n_steps): - self.make_step(n_steps) - self.render_progress() + def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. - def finish(self): - self.eta_known = 0 + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False self.current_item = None self.finished = True - def generator(self): - """ - Returns a generator which yields the items added to the bar during - construction, and updates the progress bar *after* the yielded block - returns. + def generator(self) -> t.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. if not self.entered: - raise RuntimeError('You need to use progress bars in a with block.') + raise RuntimeError("You need to use progress bars in a with block.") if self.is_hidden: - for rv in self.iter: - yield rv + yield from self.iter else: for rv in self.iter: self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + yield rv self.update(1) + self.finish() self.render_progress() -def pager(generator, color=None): +def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: """Decide what method to use for paging through text.""" stdout = _default_text_stdout() if not isatty(sys.stdin) or not isatty(stdout): return _nullpager(stdout, generator, color) - pager_cmd = (os.environ.get('PAGER', None) or '').strip() + pager_cmd = (os.environ.get("PAGER", None) or "").strip() if pager_cmd: if WIN: return _tempfilepager(generator, pager_cmd, color) return _pipepager(generator, pager_cmd, color) - if os.environ.get('TERM') in ('dumb', 'emacs'): + if os.environ.get("TERM") in ("dumb", "emacs"): return _nullpager(stdout, generator, color) - if WIN or sys.platform.startswith('os2'): - return _tempfilepager(generator, 'more <', color) - if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: - return _pipepager(generator, 'less', color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) import tempfile + fd, filename = tempfile.mkstemp() os.close(fd) try: - if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: - return _pipepager(generator, 'more', color) + if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: + return _pipepager(generator, "more", color) return _nullpager(stdout, generator, color) finally: os.unlink(filename) -def _pipepager(generator, cmd, color): +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: """Page through text by feeding it to another program. Invoking a pager through this might support colors. """ import subprocess + env = dict(os.environ) # If we're piping to less we might support colors under the # condition that - cmd_detail = cmd.rsplit('/', 1)[-1].split() - if color is None and cmd_detail[0] == 'less': - less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:]) + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" if not less_flags: - env['LESS'] = '-R' + env["LESS"] = "-R" color = True - elif 'r' in less_flags or 'R' in less_flags: + elif "r" in less_flags or "R" in less_flags: color = True - c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - env=env) - encoding = get_best_encoding(c.stdin) + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + stdin = t.cast(t.BinaryIO, c.stdin) + encoding = get_best_encoding(stdin) try: for text in generator: if not color: text = strip_ansi(text) - c.stdin.write(text.encode(encoding, 'replace')) - except (IOError, KeyboardInterrupt): + stdin.write(text.encode(encoding, "replace")) + except (OSError, KeyboardInterrupt): pass else: - c.stdin.close() + stdin.close() # Less doesn't respect ^C, but catches it for its own UI purposes (aborting # search or other commands inside less). @@ -367,24 +420,30 @@ def _pipepager(generator, cmd, color): break -def _tempfilepager(generator, cmd, color): +def _tempfilepager( + generator: t.Iterable[str], cmd: str, color: t.Optional[bool] +) -> None: """Page through text by invoking a program on a temporary file.""" import tempfile - filename = tempfile.mktemp() + + fd, filename = tempfile.mkstemp() # TODO: This never terminates if the passed generator never terminates. text = "".join(generator) if not color: text = strip_ansi(text) encoding = get_best_encoding(sys.stdout) - with open_stream(filename, 'wb')[0] as f: + with open_stream(filename, "wb")[0] as f: f.write(text.encode(encoding)) try: - os.system(cmd + ' "' + filename + '"') + os.system(f'{cmd} "{filename}"') finally: + os.close(fd) os.unlink(filename) -def _nullpager(stream, generator, color): +def _nullpager( + stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] +) -> None: """Simply print unformatted text. This is the ultimate fallback.""" for text in generator: if not color: @@ -392,159 +451,184 @@ def _nullpager(stream, generator, color): stream.write(text) -class Editor(object): - - def __init__(self, editor=None, env=None, require_save=True, - extension='.txt'): +class Editor: + def __init__( + self, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: self.editor = editor self.env = env self.require_save = require_save self.extension = extension - def get_editor(self): + def get_editor(self) -> str: if self.editor is not None: return self.editor - for key in 'VISUAL', 'EDITOR': + for key in "VISUAL", "EDITOR": rv = os.environ.get(key) if rv: return rv if WIN: - return 'notepad' - for editor in 'vim', 'nano': - if os.system('which %s >/dev/null 2>&1' % editor) == 0: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system(f"which {editor} >/dev/null 2>&1") == 0: return editor - return 'vi' + return "vi" - def edit_file(self, filename): + def edit_file(self, filename: str) -> None: import subprocess + editor = self.get_editor() + environ: t.Optional[t.Dict[str, str]] = None + if self.env: environ = os.environ.copy() environ.update(self.env) - else: - environ = None + try: - c = subprocess.Popen('%s "%s"' % (editor, filename), - env=environ, shell=True) + c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True) exit_code = c.wait() if exit_code != 0: - raise ClickException('%s: Editing failed!' % editor) + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) except OSError as e: - raise ClickException('%s: Editing failed: %s' % (editor, e)) + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e - def edit(self, text): + def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: import tempfile - text = text or '' - if text and not text.endswith('\n'): - text += '\n' + if not text: + data = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" - fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension) - try: if WIN: - encoding = 'utf-8-sig' - text = text.replace('\n', '\r\n') + data = text.replace("\n", "\r\n").encode("utf-8-sig") else: - encoding = 'utf-8' - text = text.encode(encoding) + data = text.encode("utf-8") - f = os.fdopen(fd, 'wb') - f.write(text) - f.close() + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. timestamp = os.path.getmtime(name) self.edit_file(name) - if self.require_save \ - and os.path.getmtime(name) == timestamp: + if self.require_save and os.path.getmtime(name) == timestamp: return None - f = open(name, 'rb') - try: + with open(name, "rb") as f: rv = f.read() - finally: - f.close() - return rv.decode('utf-8-sig').replace('\r\n', '\n') + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore finally: os.unlink(name) -def open_url(url, wait=False, locate=False): +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: import subprocess - def _unquote_file(url): - try: - import urllib - except ImportError: - import urllib - if url.startswith('file://'): - url = urllib.unquote(url[7:]) + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + return url - if sys.platform == 'darwin': - args = ['open'] + if sys.platform == "darwin": + args = ["open"] if wait: - args.append('-W') + args.append("-W") if locate: - args.append('-R') + args.append("-R") args.append(_unquote_file(url)) - null = open('/dev/null', 'w') + null = open("/dev/null", "w") try: return subprocess.Popen(args, stderr=null).wait() finally: null.close() elif WIN: if locate: - url = _unquote_file(url) - args = 'explorer /select,"%s"' % _unquote_file( - url.replace('"', '')) + url = _unquote_file(url.replace('"', "")) + args = f'explorer /select,"{url}"' else: - args = 'start %s "" "%s"' % ( - wait and '/WAIT' or '', url.replace('"', '')) + url = url.replace('"', "") + wait_str = "/WAIT" if wait else "" + args = f'start {wait_str} "" "{url}"' return os.system(args) elif CYGWIN: if locate: - url = _unquote_file(url) - args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', '')) + url = os.path.dirname(_unquote_file(url).replace('"', "")) + args = f'cygstart "{url}"' else: - args = 'cygstart %s "%s"' % ( - wait and '-w' or '', url.replace('"', '')) + url = url.replace('"', "") + wait_str = "-w" if wait else "" + args = f'cygstart {wait_str} "{url}"' return os.system(args) try: if locate: - url = os.path.dirname(_unquote_file(url)) or '.' + url = os.path.dirname(_unquote_file(url)) or "." else: url = _unquote_file(url) - c = subprocess.Popen(['xdg-open', url]) + c = subprocess.Popen(["xdg-open", url]) if wait: return c.wait() return 0 except OSError: - if url.startswith(('http://', 'https://')) and not locate and not wait: + if url.startswith(("http://", "https://")) and not locate and not wait: import webbrowser + webbrowser.open(url) return 0 return 1 -def _translate_ch_to_exc(ch): - if ch == u'\x03': +def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]: + if ch == "\x03": raise KeyboardInterrupt() - if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D raise EOFError() - if ch == u'\x1a' and WIN: # Windows, Ctrl+Z + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z raise EOFError() + return None + if WIN: import msvcrt @contextlib.contextmanager - def raw_terminal(): - yield + def raw_terminal() -> t.Iterator[int]: + yield -1 - def getchar(echo): + def getchar(echo: bool) -> str: # The function `getch` will return a bytes object corresponding to # the pressed character. Since Windows 10 build 1803, it will also # return \x00 when called a second time after pressing a regular key. @@ -574,48 +658,60 @@ if WIN: # # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` # is doing the right thing in more situations than with `getch`. + func: t.Callable[[], str] + if echo: - func = msvcrt.getwche + func = msvcrt.getwche # type: ignore else: - func = msvcrt.getwch + func = msvcrt.getwch # type: ignore rv = func() - if rv in (u'\x00', u'\xe0'): + + if rv in ("\x00", "\xe0"): # \x00 and \xe0 are control characters that indicate special key, # see above. rv += func() + _translate_ch_to_exc(rv) return rv + else: import tty import termios @contextlib.contextmanager - def raw_terminal(): + def raw_terminal() -> t.Iterator[int]: + f: t.Optional[t.TextIO] + fd: int + if not isatty(sys.stdin): - f = open('/dev/tty') + f = open("/dev/tty") fd = f.fileno() else: fd = sys.stdin.fileno() f = None + try: old_settings = termios.tcgetattr(fd) + try: tty.setraw(fd) yield fd finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) sys.stdout.flush() + if f is not None: f.close() except termios.error: pass - def getchar(echo): + def getchar(echo: bool) -> str: with raw_terminal() as fd: - ch = os.read(fd, 32) - ch = ch.decode(get_best_encoding(sys.stdin), 'replace') + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + if echo and isatty(sys.stdout): sys.stdout.write(ch) + _translate_ch_to_exc(ch) return ch diff --git a/libs/common/click/_textwrap.py b/libs/common/click/_textwrap.py index 7e776031..b47dcbd4 100644 --- a/libs/common/click/_textwrap.py +++ b/libs/common/click/_textwrap.py @@ -1,10 +1,16 @@ import textwrap +import typing as t from contextlib import contextmanager class TextWrapper(textwrap.TextWrapper): - - def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + def _handle_long_word( + self, + reversed_chunks: t.List[str], + cur_line: t.List[str], + cur_len: int, + width: int, + ) -> None: space_left = max(width - cur_len, 1) if self.break_long_words: @@ -17,22 +23,27 @@ class TextWrapper(textwrap.TextWrapper): cur_line.append(reversed_chunks.pop()) @contextmanager - def extra_indent(self, indent): + def extra_indent(self, indent: str) -> t.Iterator[None]: old_initial_indent = self.initial_indent old_subsequent_indent = self.subsequent_indent self.initial_indent += indent self.subsequent_indent += indent + try: yield finally: self.initial_indent = old_initial_indent self.subsequent_indent = old_subsequent_indent - def indent_only(self, text): + def indent_only(self, text: str) -> str: rv = [] + for idx, line in enumerate(text.splitlines()): indent = self.initial_indent + if idx > 0: indent = self.subsequent_indent - rv.append(indent + line) - return '\n'.join(rv) + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/libs/common/click/_unicodefun.py b/libs/common/click/_unicodefun.py deleted file mode 100644 index 620edff3..00000000 --- a/libs/common/click/_unicodefun.py +++ /dev/null @@ -1,125 +0,0 @@ -import os -import sys -import codecs - -from ._compat import PY2 - - -# If someone wants to vendor click, we want to ensure the -# correct package is discovered. Ideally we could use a -# relative import here but unfortunately Python does not -# support that. -click = sys.modules[__name__.rsplit('.', 1)[0]] - - -def _find_unicode_literals_frame(): - import __future__ - if not hasattr(sys, '_getframe'): # not all Python implementations have it - return 0 - frm = sys._getframe(1) - idx = 1 - while frm is not None: - if frm.f_globals.get('__name__', '').startswith('click.'): - frm = frm.f_back - idx += 1 - elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: - return idx - else: - break - return 0 - - -def _check_for_unicode_literals(): - if not __debug__: - return - if not PY2 or click.disable_unicode_literals_warning: - return - bad_frame = _find_unicode_literals_frame() - if bad_frame <= 0: - return - from warnings import warn - warn(Warning('Click detected the use of the unicode_literals ' - '__future__ import. This is heavily discouraged ' - 'because it can introduce subtle bugs in your ' - 'code. You should instead use explicit u"" literals ' - 'for your unicode strings. For more information see ' - 'https://click.palletsprojects.com/python3/'), - stacklevel=bad_frame) - - -def _verify_python3_env(): - """Ensures that the environment is good for unicode on Python 3.""" - if PY2: - return - try: - import locale - fs_enc = codecs.lookup(locale.getpreferredencoding()).name - except Exception: - fs_enc = 'ascii' - if fs_enc != 'ascii': - return - - extra = '' - if os.name == 'posix': - import subprocess - try: - rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate()[0] - except OSError: - rv = b'' - good_locales = set() - has_c_utf8 = False - - # Make sure we're operating on text here. - if isinstance(rv, bytes): - rv = rv.decode('ascii', 'replace') - - for line in rv.splitlines(): - locale = line.strip() - if locale.lower().endswith(('.utf-8', '.utf8')): - good_locales.add(locale) - if locale.lower() in ('c.utf8', 'c.utf-8'): - has_c_utf8 = True - - extra += '\n\n' - if not good_locales: - extra += ( - 'Additional information: on this system no suitable UTF-8\n' - 'locales were discovered. This most likely requires resolving\n' - 'by reconfiguring the locale system.' - ) - elif has_c_utf8: - extra += ( - 'This system supports the C.UTF-8 locale which is recommended.\n' - 'You might be able to resolve your issue by exporting the\n' - 'following environment variables:\n\n' - ' export LC_ALL=C.UTF-8\n' - ' export LANG=C.UTF-8' - ) - else: - extra += ( - 'This system lists a couple of UTF-8 supporting locales that\n' - 'you can pick from. The following suitable locales were\n' - 'discovered: %s' - ) % ', '.join(sorted(good_locales)) - - bad_locale = None - for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'): - if locale and locale.lower().endswith(('.utf-8', '.utf8')): - bad_locale = locale - if locale is not None: - break - if bad_locale is not None: - extra += ( - '\n\nClick discovered that you exported a UTF-8 locale\n' - 'but the locale system could not pick up from it because\n' - 'it does not exist. The exported locale is "%s" but it\n' - 'is not supported' - ) % bad_locale - - raise RuntimeError( - 'Click will abort further execution because Python 3 was' - ' configured to use ASCII as encoding for the environment.' - ' Consult https://click.palletsprojects.com/en/7.x/python3/ for' - ' mitigation steps.' + extra - ) diff --git a/libs/common/click/_winconsole.py b/libs/common/click/_winconsole.py index bbb080dd..6b20df31 100644 --- a/libs/common/click/_winconsole.py +++ b/libs/common/click/_winconsole.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This module is based on the excellent work by Adam Bartoš who # provided a lot of what went into the implementation here in # the discussion to issue1602 in the Python bug tracker. @@ -6,26 +5,32 @@ # There are some general differences in regards to how this works # compared to the original patches as we do not need to patch # the entire interpreter but just work in our little world of -# echo and prmopt. - +# echo and prompt. import io -import os import sys -import zlib import time -import ctypes -import msvcrt -from ._compat import _NonClosingTextIOWrapper, text_type, PY2 -from ctypes import byref, POINTER, c_int, c_char, c_char_p, \ - c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE -try: - from ctypes import pythonapi - PyObject_GetBuffer = pythonapi.PyObject_GetBuffer - PyBuffer_Release = pythonapi.PyBuffer_Release -except ImportError: - pythonapi = None -from ctypes.wintypes import LPWSTR, LPCWSTR +import typing as t +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 c_ssize_p = POINTER(c_ssize_t) @@ -33,19 +38,18 @@ kernel32 = windll.kernel32 GetStdHandle = kernel32.GetStdHandle ReadConsoleW = kernel32.ReadConsoleW WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode GetLastError = kernel32.GetLastError -GetCommandLineW = WINFUNCTYPE(LPWSTR)( - ('GetCommandLineW', windll.kernel32)) -CommandLineToArgvW = WINFUNCTYPE( - POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( - ('CommandLineToArgvW', windll.shell32)) - +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) STDIN_HANDLE = GetStdHandle(-10) STDOUT_HANDLE = GetStdHandle(-11) STDERR_HANDLE = GetStdHandle(-12) - PyBUF_SIMPLE = 0 PyBUF_WRITABLE = 1 @@ -57,38 +61,40 @@ STDIN_FILENO = 0 STDOUT_FILENO = 1 STDERR_FILENO = 2 -EOF = b'\x1a' +EOF = b"\x1a" MAX_BYTES_WRITTEN = 32767 - -class Py_buffer(ctypes.Structure): - _fields_ = [ - ('buf', c_void_p), - ('obj', py_object), - ('len', c_ssize_t), - ('itemsize', c_ssize_t), - ('readonly', c_int), - ('ndim', c_int), - ('format', c_char_p), - ('shape', c_ssize_p), - ('strides', c_ssize_p), - ('suboffsets', c_ssize_p), - ('internal', c_void_p) - ] - - if PY2: - _fields_.insert(-1, ('smalltable', c_ssize_t * 2)) - - -# On PyPy we cannot get buffers so our ability to operate here is -# serverly limited. -if pythonapi is None: +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. get_buffer = None else: + + class Py_buffer(Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + def get_buffer(obj, writable=False): buf = Py_buffer() flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE PyObject_GetBuffer(py_object(obj), byref(buf), flags) + try: buffer_type = c_char * buf.len return buffer_type.from_address(buf.buf) @@ -97,17 +103,15 @@ else: class _WindowsConsoleRawIOBase(io.RawIOBase): - def __init__(self, handle): self.handle = handle def isatty(self): - io.RawIOBase.isatty(self) + super().isatty() return True class _WindowsConsoleReader(_WindowsConsoleRawIOBase): - def readable(self): return True @@ -116,20 +120,26 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase): if not bytes_to_be_read: return 0 elif bytes_to_be_read % 2: - raise ValueError('cannot read odd number of bytes from ' - 'UTF-16-LE encoded console') + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) buffer = get_buffer(b, writable=True) code_units_to_be_read = bytes_to_be_read // 2 code_units_read = c_ulong() - rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read, - byref(code_units_read), None) + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) if GetLastError() == ERROR_OPERATION_ABORTED: # wait for KeyboardInterrupt time.sleep(0.1) if not rv: - raise OSError('Windows error: %s' % GetLastError()) + raise OSError(f"Windows error: {GetLastError()}") if buffer[0] == EOF: return 0 @@ -137,27 +147,30 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase): class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): - def writable(self): return True @staticmethod def _get_error_message(errno): if errno == ERROR_SUCCESS: - return 'ERROR_SUCCESS' + return "ERROR_SUCCESS" elif errno == ERROR_NOT_ENOUGH_MEMORY: - return 'ERROR_NOT_ENOUGH_MEMORY' - return 'Windows error %s' % errno + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" def write(self, b): bytes_to_be_written = len(b) buf = get_buffer(b) - code_units_to_be_written = min(bytes_to_be_written, - MAX_BYTES_WRITTEN) // 2 + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 code_units_written = c_ulong() - WriteConsoleW(self.handle, buf, code_units_to_be_written, - byref(code_units_written), None) + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) bytes_written = 2 * code_units_written.value if bytes_written == 0 and bytes_to_be_written > 0: @@ -165,18 +178,17 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): return bytes_written -class ConsoleStream(object): - - def __init__(self, text_stream, byte_stream): +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: self._text_stream = text_stream self.buffer = byte_stream @property - def name(self): + def name(self) -> str: return self.buffer.name - def write(self, x): - if isinstance(x, text_type): + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): return self._text_stream.write(x) try: self.flush() @@ -184,124 +196,84 @@ class ConsoleStream(object): pass return self.buffer.write(x) - def writelines(self, lines): + def writelines(self, lines: t.Iterable[t.AnyStr]) -> None: for line in lines: self.write(line) - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._text_stream, name) - def isatty(self): + def isatty(self) -> bool: return self.buffer.isatty() def __repr__(self): - return '' % ( - self.name, - self.encoding, - ) + return f"" -class WindowsChunkedWriter(object): - """ - Wraps a stream (such as stdout), acting as a transparent proxy for all - attribute access apart from method 'write()' which we wrap to write in - limited chunks due to a Windows limitation on binary console streams. - """ - def __init__(self, wrapped): - # double-underscore everything to prevent clashes with names of - # attributes on the wrapped stream object. - self.__wrapped = wrapped - - def __getattr__(self, name): - return getattr(self.__wrapped, name) - - def write(self, text): - total_to_write = len(text) - written = 0 - - while written < total_to_write: - to_write = min(total_to_write - written, MAX_BYTES_WRITTEN) - self.__wrapped.write(text[written:written+to_write]) - written += to_write - - -_wrapped_std_streams = set() - - -def _wrap_std_stream(name): - # Python 2 & Windows 7 and below - if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams: - setattr(sys, name, WindowsChunkedWriter(getattr(sys, name))) - _wrapped_std_streams.add(name) - - -def _get_text_stdin(buffer_stream): +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -def _get_text_stdout(buffer_stream): +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -def _get_text_stderr(buffer_stream): +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -if PY2: - def _hash_py_argv(): - return zlib.crc32('\x00'.join(sys.argv[1:])) - - _initial_argv_hash = _hash_py_argv() - - def _get_windows_argv(): - argc = c_int(0) - argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) - argv = [argv_unicode[i] for i in range(0, argc.value)] - - if not hasattr(sys, 'frozen'): - argv = argv[1:] - while len(argv) > 0: - arg = argv[0] - if not arg.startswith('-') or arg == '-': - break - argv = argv[1:] - if arg.startswith(('-c', '-m')): - break - - return argv[1:] - - -_stream_factories = { +_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { 0: _get_text_stdin, 1: _get_text_stdout, 2: _get_text_stderr, } -def _get_windows_console_stream(f, encoding, errors): - if get_buffer is not None and \ - encoding in ('utf-16-le', None) \ - and errors in ('strict', None) and \ - hasattr(f, 'isatty') and f.isatty(): +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> t.Optional[t.TextIO]: + if ( + get_buffer is not None + and encoding in {"utf-16-le", None} + and errors in {"strict", None} + and _is_console(f) + ): func = _stream_factories.get(f.fileno()) if func is not None: - if not PY2: - f = getattr(f, 'buffer', None) - if f is None: - return None - else: - # If we are on Python 2 we need to set the stream that we - # deal with to binary mode as otherwise the exercise if a - # bit moot. The same problems apply as for - # get_binary_stdin and friends from _compat. - msvcrt.setmode(f.fileno(), os.O_BINARY) - return func(f) + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/libs/common/click/core.py b/libs/common/click/core.py index 7a1e3422..5abfb0f3 100644 --- a/libs/common/click/core.py +++ b/libs/common/click/core.py @@ -1,106 +1,102 @@ +import enum import errno import inspect import os import sys +import typing as t +from collections import abc from contextlib import contextmanager -from itertools import repeat +from contextlib import ExitStack +from functools import partial from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat -from .types import convert_type, IntRange, BOOL -from .utils import PacifyFlushWrapper, make_str, make_default_short_help, \ - echo, get_os_args -from .exceptions import ClickException, UsageError, BadParameter, Abort, \ - MissingParameter, Exit -from .termui import prompt, confirm, style -from .formatting import HelpFormatter, join_options -from .parser import OptionParser, split_opt -from .globals import push_context, pop_context +from . import types +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _flag_needs_value +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper -from ._compat import PY2, isidentifier, iteritems, string_types -from ._unicodefun import _check_for_unicode_literals, _verify_python3_env +if t.TYPE_CHECKING: + import typing_extensions as te + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +V = t.TypeVar("V") -_missing = object() +def _complete_visible_commands( + ctx: "Context", incomplete: str +) -> t.Iterator[t.Tuple[str, "Command"]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. - -SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...' -SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' - -DEPRECATED_HELP_NOTICE = ' (DEPRECATED)' -DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \ - 'The command %(name)s is deprecated.' - - -def _maybe_show_deprecated_notice(cmd): - if cmd.deprecated: - echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True) - - -def fast_exit(code): - """Exit without garbage collection, this speeds up exit by about 10ms for - things like bash completion. + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. """ - sys.stdout.flush() - sys.stderr.flush() - os._exit(code) + multi = t.cast(MultiCommand, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command -def _bashcomplete(cmd, prog_name, complete_var=None): - """Internal handler for the bash completion support.""" - if complete_var is None: - complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper() - complete_instr = os.environ.get(complete_var) - if not complete_instr: - return - - from ._bashcomplete import bashcomplete - if bashcomplete(cmd, prog_name, complete_var, complete_instr): - fast_exit(1) - - -def _check_multicommand(base_command, cmd_name, cmd, register=False): +def _check_multicommand( + base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False +) -> None: if not base_command.chain or not isinstance(cmd, MultiCommand): return if register: - hint = 'It is not possible to add multi commands as children to ' \ - 'another multi command that is in chain mode' + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) else: - hint = 'Found a multi command as subcommand to a multi command ' \ - 'that is in chain mode. This is not supported' - raise RuntimeError('%s. Command "%s" is set to chain and "%s" was ' - 'added as subcommand but it in itself is a ' - 'multi command. ("%s" is a %s within a chained ' - '%s named "%s").' % ( - hint, base_command.name, cmd_name, - cmd_name, cmd.__class__.__name__, - base_command.__class__.__name__, - base_command.name)) + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + f"{hint}. Command {base_command.name!r} is set to chain and" + f" {cmd_name!r} was added as a subcommand but it in itself is a" + f" multi command. ({cmd_name!r} is a {type(cmd).__name__}" + f" within a chained {type(base_command).__name__} named" + f" {base_command.name!r})." + ) -def batch(iterable, batch_size): +def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]: return list(zip(*repeat(iter(iterable), batch_size))) -def invoke_param_callback(callback, ctx, param, value): - code = getattr(callback, '__code__', None) - args = getattr(code, 'co_argcount', 3) - - if args < 3: - # This will become a warning in Click 3.0: - from warnings import warn - warn(Warning('Invoked legacy parameter callback "%s". The new ' - 'signature for such callbacks starting with ' - 'click 2.0 is (ctx, param, value).' - % callback), stacklevel=3) - return callback(ctx, value) - return callback(ctx, param, value) - - @contextmanager -def augment_usage_errors(ctx, param=None): - """Context manager that attaches extra information to exceptions that - fly. - """ +def augment_usage_errors( + ctx: "Context", param: t.Optional["Parameter"] = None +) -> t.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" try: yield except BadParameter as e: @@ -115,22 +111,53 @@ def augment_usage_errors(ctx, param=None): raise -def iter_params_for_processing(invocation_order, declaration_order): +def iter_params_for_processing( + invocation_order: t.Sequence["Parameter"], + declaration_order: t.Sequence["Parameter"], +) -> t.List["Parameter"]: """Given a sequence of parameters in the order as should be considered for processing and an iterable of parameters that exist, this returns a list in the correct order as they should be processed. """ - def sort_key(item): + + def sort_key(item: "Parameter") -> t.Tuple[bool, float]: try: - idx = invocation_order.index(item) + idx: float = invocation_order.index(item) except ValueError: - idx = float('inf') - return (not item.is_eager, idx) + idx = float("inf") + + return not item.is_eager, idx return sorted(declaration_order, key=sort_key) -class Context(object): +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: """The context is a special internal object that holds state relevant for the script execution at every single level. It's normally invisible to commands unless they opt-in to getting access to it. @@ -142,18 +169,6 @@ class Context(object): A context can be used as context manager in which case it will call :meth:`close` on teardown. - .. versionadded:: 2.0 - Added the `resilient_parsing`, `help_option_names`, - `token_normalize_func` parameters. - - .. versionadded:: 3.0 - Added the `allow_extra_args` and `allow_interspersed_args` - parameters. - - .. versionadded:: 4.0 - Added the `color`, `ignore_unknown_options`, and - `max_content_width` parameters. - :param command: the command class for this context. :param parent: the parent context. :param info_name: the info name for this invocation. Generally this @@ -208,43 +223,95 @@ class Context(object): codes are used in texts that Click prints which is by default not the case. This for instance would affect help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. """ - def __init__(self, command, parent=None, info_name=None, obj=None, - auto_envvar_prefix=None, default_map=None, - terminal_width=None, max_content_width=None, - resilient_parsing=False, allow_extra_args=None, - allow_interspersed_args=None, - ignore_unknown_options=None, help_option_names=None, - token_normalize_func=None, color=None): + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: t.Type["HelpFormatter"] = HelpFormatter + + def __init__( + self, + command: "Command", + parent: t.Optional["Context"] = None, + info_name: t.Optional[str] = None, + obj: t.Optional[t.Any] = None, + auto_envvar_prefix: t.Optional[str] = None, + default_map: t.Optional[t.Dict[str, t.Any]] = None, + terminal_width: t.Optional[int] = None, + max_content_width: t.Optional[int] = None, + resilient_parsing: bool = False, + allow_extra_args: t.Optional[bool] = None, + allow_interspersed_args: t.Optional[bool] = None, + ignore_unknown_options: t.Optional[bool] = None, + help_option_names: t.Optional[t.List[str]] = None, + token_normalize_func: t.Optional[t.Callable[[str], str]] = None, + color: t.Optional[bool] = None, + show_default: t.Optional[bool] = None, + ) -> None: #: the parent context or `None` if none exists. self.parent = parent #: the :class:`Command` for this context. self.command = command #: the descriptive information name self.info_name = info_name - #: the parsed parameters except if the value is hidden in which - #: case it's not remembered. - self.params = {} + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: t.Dict[str, t.Any] = {} #: the leftover arguments. - self.args = [] + self.args: t.List[str] = [] #: protected arguments. These are arguments that are prepended #: to `args` when certain parsing scenarios are encountered but #: must be never propagated to another arguments. This is used #: to implement nested parsing. - self.protected_args = [] + self.protected_args: t.List[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set() + if obj is None and parent is not None: obj = parent.obj + #: the user object stored. - self.obj = obj - self._meta = getattr(parent, 'meta', {}) + self.obj: t.Any = obj + self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {}) #: A dictionary (-like object) with defaults for parameters. - if default_map is None \ - and parent is not None \ - and parent.default_map is not None: + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): default_map = parent.default_map.get(info_name) - self.default_map = default_map + + self.default_map: t.Optional[t.Dict[str, t.Any]] = default_map #: This flag indicates if a subcommand is going to be executed. A #: group callback can use this information to figure out if it's @@ -255,22 +322,25 @@ class Context(object): #: If chaining is enabled this will be set to ``'*'`` in case #: any commands are executed. It is however not possible to #: figure out which ones. If you require this knowledge you - #: should use a :func:`resultcallback`. - self.invoked_subcommand = None + #: should use a :func:`result_callback`. + self.invoked_subcommand: t.Optional[str] = None if terminal_width is None and parent is not None: terminal_width = parent.terminal_width + #: The width of the terminal (None is autodetection). - self.terminal_width = terminal_width + self.terminal_width: t.Optional[int] = terminal_width if max_content_width is None and parent is not None: max_content_width = parent.max_content_width + #: The maximum width of formatted content (None implies a sensible #: default which is 80 for most things). - self.max_content_width = max_content_width + self.max_content_width: t.Optional[int] = max_content_width if allow_extra_args is None: allow_extra_args = command.allow_extra_args + #: Indicates if the context allows extra args or if it should #: fail on parsing. #: @@ -279,14 +349,16 @@ class Context(object): if allow_interspersed_args is None: allow_interspersed_args = command.allow_interspersed_args + #: Indicates if the context allows mixing of arguments and #: options or not. #: #: .. versionadded:: 3.0 - self.allow_interspersed_args = allow_interspersed_args + self.allow_interspersed_args: bool = allow_interspersed_args if ignore_unknown_options is None: ignore_unknown_options = command.ignore_unknown_options + #: Instructs click to ignore options that a command does not #: understand and will store it on the context for later #: processing. This is primarily useful for situations where you @@ -295,64 +367,102 @@ class Context(object): #: forward all arguments. #: #: .. versionadded:: 4.0 - self.ignore_unknown_options = ignore_unknown_options + self.ignore_unknown_options: bool = ignore_unknown_options if help_option_names is None: if parent is not None: help_option_names = parent.help_option_names else: - help_option_names = ['--help'] + help_option_names = ["--help"] #: The names for the help options. - self.help_option_names = help_option_names + self.help_option_names: t.List[str] = help_option_names if token_normalize_func is None and parent is not None: token_normalize_func = parent.token_normalize_func #: An optional normalization function for tokens. This is #: options, choices, commands etc. - self.token_normalize_func = token_normalize_func + self.token_normalize_func: t.Optional[ + t.Callable[[str], str] + ] = token_normalize_func #: Indicates if resilient parsing is enabled. In that case Click #: will do its best to not cause any failures and default values #: will be ignored. Useful for completion. - self.resilient_parsing = resilient_parsing + self.resilient_parsing: bool = resilient_parsing # If there is no envvar prefix yet, but the parent has one and # the command on this level has a name, we can expand the envvar # prefix automatically. if auto_envvar_prefix is None: - if parent is not None \ - and parent.auto_envvar_prefix is not None and \ - self.info_name is not None: - auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix, - self.info_name.upper()) + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) else: auto_envvar_prefix = auto_envvar_prefix.upper() - self.auto_envvar_prefix = auto_envvar_prefix + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix if color is None and parent is not None: color = parent.color #: Controls if styling output is wanted or not. - self.color = color + self.color: t.Optional[bool] = color - self._close_callbacks = [] + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: t.Optional[bool] = show_default + + self._close_callbacks: t.List[t.Callable[[], t.Any]] = [] self._depth = 0 + self._parameter_source: t.Dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() - def __enter__(self): + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> "Context": self._depth += 1 push_context(self) return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self._depth -= 1 if self._depth == 0: self.close() pop_context() @contextmanager - def scope(self, cleanup=True): + def scope(self, cleanup: bool = True) -> t.Iterator["Context"]: """This helper method can be used with the context object to promote it to the current thread local (see :func:`get_current_context`). The default behavior of this is to invoke the cleanup functions which @@ -390,7 +500,7 @@ class Context(object): self._depth -= 1 @property - def meta(self): + def meta(self) -> t.Dict[str, t.Any]: """This is a dictionary which is shared with all the contexts that are nested. It exists so that click utilities can store some state here if they need to. It is however the responsibility of @@ -404,7 +514,7 @@ class Context(object): Example usage:: - LANG_KEY = __name__ + '.lang' + LANG_KEY = f'{__name__}.lang' def set_language(value): ctx = get_current_context() @@ -417,58 +527,109 @@ class Context(object): """ return self._meta - def make_formatter(self): - """Creates the formatter for the help and usage output.""" - return HelpFormatter(width=self.terminal_width, - max_width=self.max_content_width) + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. - def call_on_close(self, f): - """This decorator remembers a function as callback that should be - executed when the context tears down. This is most useful to bind - resource handling to the script execution. For instance, file objects - opened by the :class:`File` type will register their close callbacks - here. + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. - :param f: the function to execute on teardown. + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. """ - self._close_callbacks.append(f) - return f + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) - def close(self): - """Invokes all close callbacks.""" - for cb in self._close_callbacks: - cb() - self._close_callbacks = [] + def with_resource(self, context_manager: t.ContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() @property - def command_path(self): + def command_path(self) -> str: """The computed command path. This is used for the ``usage`` information on the help page. It's automatically created by combining the info names of the chain of contexts to the root. """ - rv = '' + rv = "" if self.info_name is not None: rv = self.info_name if self.parent is not None: - rv = self.parent.command_path + ' ' + rv + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" return rv.lstrip() - def find_root(self): + def find_root(self) -> "Context": """Finds the outermost context.""" node = self while node.parent is not None: node = node.parent return node - def find_object(self, object_type): + def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: """Finds the closest object of a given type.""" - node = self + node: t.Optional["Context"] = self + while node is not None: if isinstance(node.obj, object_type): return node.obj + node = node.parent - def ensure_object(self, object_type): + return None + + def ensure_object(self, object_type: t.Type[V]) -> V: """Like :meth:`find_object` but sets the innermost object to a new instance of `object_type` if it does not exist. """ @@ -477,17 +638,39 @@ class Context(object): self.obj = rv = object_type() return rv - def lookup_default(self, name): - """Looks up the default for a parameter name. This by default - looks into the :attr:`default_map` if available. + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[False]" = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. """ if self.default_map is not None: - rv = self.default_map.get(name) - if callable(rv): - rv = rv() - return rv + value = self.default_map.get(name) - def fail(self, message): + if call and callable(value): + return value() + + return value + + return None + + def fail(self, message: str) -> "te.NoReturn": """Aborts the execution of the program with a specific error message. @@ -495,27 +678,40 @@ class Context(object): """ raise UsageError(message, self) - def abort(self): + def abort(self) -> "te.NoReturn": """Aborts the script.""" raise Abort() - def exit(self, code=0): + def exit(self, code: int = 0) -> "te.NoReturn": """Exits the application with a given exit code.""" raise Exit(code) - def get_usage(self): + def get_usage(self) -> str: """Helper method to get formatted usage string for the current context and command. """ return self.command.get_usage(self) - def get_help(self): + def get_help(self) -> str: """Helper method to get formatted help page for the current context and command. """ return self.command.get_help(self) - def invoke(*args, **kwargs): + def _make_sub_context(self, command: "Command") -> "Context": + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + def invoke( + __self, # noqa: B902 + __callback: t.Union["Command", t.Callable[..., t.Any]], + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: """Invokes a command callback in exactly the way it expects. There are two ways to invoke this method: @@ -530,50 +726,89 @@ class Context(object): in against the intention of this code and no context was created. For more information about this change and why it was done in a bugfix release see :ref:`upgrade-to-3.2`. - """ - self, callback = args[:2] - ctx = self - # It's also possible to invoke another command which might or - # might not have a callback. In that case we also fill - # in defaults and make a new context for this command. - if isinstance(callback, Command): - other_cmd = callback - callback = other_cmd.callback - ctx = Context(other_cmd, info_name=other_cmd.name, parent=self) - if callback is None: - raise TypeError('The given command does not have a ' - 'callback that can be invoked.') + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + """ + if isinstance(__callback, Command): + other_cmd = __callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + __callback = other_cmd.callback + + ctx = __self._make_sub_context(other_cmd) for param in other_cmd.params: if param.name not in kwargs and param.expose_value: - kwargs[param.name] = param.get_default(ctx) + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) - args = args[2:] - with augment_usage_errors(self): + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = __self + + with augment_usage_errors(__self): with ctx: - return callback(*args, **kwargs) + return __callback(*args, **kwargs) - def forward(*args, **kwargs): + def forward( + __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Any: """Similar to :meth:`invoke` but fills in default keyword arguments from the current context if the other command expects it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. """ - self, cmd = args[:2] + # Can only forward to other commands, not direct callbacks. + if not isinstance(__cmd, Command): + raise TypeError("Callback is not a command.") - # It's also possible to invoke another command which might or - # might not have a callback. - if not isinstance(cmd, Command): - raise TypeError('Callback is not a command.') - - for param in self.params: + for param in __self.params: if param not in kwargs: - kwargs[param] = self.params[param] + kwargs[param] = __self.params[param] - return self.invoke(cmd, **kwargs) + return __self.invoke(__cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) -class BaseCommand(object): +class BaseCommand: """The base command implements the minimal API contract of commands. Most code will never use this as it does not implement a lot of useful functionality but it can act as the direct subclass of alternative @@ -594,6 +829,11 @@ class BaseCommand(object): :param context_settings: an optional dictionary with defaults that are passed to the context object. """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: t.Type[Context] = Context #: the default for the :attr:`Context.allow_extra_args` flag. allow_extra_args = False #: the default for the :attr:`Context.allow_interspersed_args` flag. @@ -601,62 +841,158 @@ class BaseCommand(object): #: the default for the :attr:`Context.ignore_unknown_options` flag. ignore_unknown_options = False - def __init__(self, name, context_settings=None): + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + ) -> None: #: the name the command thinks it has. Upon registering a command #: on a :class:`Group` the group will default the command name #: with this information. You should instead use the #: :class:`Context`\'s :attr:`~Context.info_name` attribute. self.name = name + if context_settings is None: context_settings = {} + #: an optional dictionary with defaults passed to the context. - self.context_settings = context_settings + self.context_settings: t.Dict[str, t.Any] = context_settings - def get_usage(self, ctx): - raise NotImplementedError('Base commands cannot get usage') + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire structure + below this command. - def get_help(self, ctx): - raise NotImplementedError('Base commands cannot get help') + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. - def make_context(self, info_name, args, parent=None, **extra): + :param ctx: A :class:`Context` representing this command. + + .. versionadded:: 8.0 + """ + return {"name": self.name} + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get help") + + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[Context] = None, + **extra: t.Any, + ) -> Context: """This function when given an info name and arguments will kick off the parsing and create a new :class:`Context`. It does not invoke the actual command callback though. - :param info_name: the info name for this invokation. Generally this + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this is the most descriptive name for the script or command. For the toplevel script it's usually the name of the script, for commands below it it's - the name of the script. + the name of the command. :param args: the arguments to parse as list of strings. :param parent: the parent context if available. :param extra: extra keyword arguments forwarded to the context constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. """ - for key, value in iteritems(self.context_settings): + for key, value in self.context_settings.items(): if key not in extra: extra[key] = value - ctx = Context(self, info_name=info_name, parent=parent, **extra) + + ctx = self.context_class( + self, info_name=info_name, parent=parent, **extra # type: ignore + ) + with ctx.scope(cleanup=False): self.parse_args(ctx, args) return ctx - def parse_args(self, ctx, args): + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: """Given a context and a list of arguments this creates the parser and parses the arguments, then modifies the context as necessary. This is automatically invoked by :meth:`make_context`. """ - raise NotImplementedError('Base commands do not know how to parse ' - 'arguments.') + raise NotImplementedError("Base commands do not know how to parse arguments.") - def invoke(self, ctx): + def invoke(self, ctx: Context) -> t.Any: """Given a context, this invokes the command. The default implementation is raising a not implemented error. """ - raise NotImplementedError('Base commands are not invokable by default') + raise NotImplementedError("Base commands are not invokable by default") - def main(self, args=None, prog_name=None, complete_var=None, - standalone_mode=True, **extra): + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. Other + command classes will return more completions. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx.protected_args + ) + + return results + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: "te.Literal[True]" = True, + **extra: t.Any, + ) -> "te.NoReturn": + ... + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: + ... + + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: """This is the way to invoke a script with all the bells and whistles as a command line application. This will always terminate the application after a call. If this is not wanted, ``SystemExit`` @@ -665,9 +1001,6 @@ class BaseCommand(object): This method is also available by directly calling the instance of a :class:`Command`. - .. versionadded:: 3.0 - Added the `standalone_mode` flag to control the standalone mode. - :param args: the arguments that should be used for parsing. If not provided, ``sys.argv[1:]`` is used. :param prog_name: the program name that should be used. By default @@ -686,30 +1019,35 @@ class BaseCommand(object): propagated to the caller and the return value of this function is the return value of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. :param extra: extra keyword arguments are forwarded to the context constructor. See :class:`Context` for more information. - """ - # If we are in Python 3, we will verify that the environment is - # sane at this point or reject further execution to avoid a - # broken script. - if not PY2: - _verify_python3_env() - else: - _check_for_unicode_literals() + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ if args is None: - args = get_os_args() + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) else: args = list(args) if prog_name is None: - prog_name = make_str(os.path.basename( - sys.argv and sys.argv[0] or __file__)) + prog_name = _detect_program_name() - # Hook for the Bash completion. This only activates if the Bash - # completion is actually enabled, otherwise this is quite a fast - # noop. - _bashcomplete(self, prog_name, complete_var) + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) try: try: @@ -727,16 +1065,16 @@ class BaseCommand(object): ctx.exit() except (EOFError, KeyboardInterrupt): echo(file=sys.stderr) - raise Abort() + raise Abort() from None except ClickException as e: if not standalone_mode: raise e.show() sys.exit(e.exit_code) - except IOError as e: + except OSError as e: if e.errno == errno.EPIPE: - sys.stdout = PacifyFlushWrapper(sys.stdout) - sys.stderr = PacifyFlushWrapper(sys.stderr) + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) sys.exit(1) else: raise @@ -756,10 +1094,38 @@ class BaseCommand(object): except Abort: if not standalone_mode: raise - echo('Aborted!', file=sys.stderr) + echo(_("Aborted!"), file=sys.stderr) sys.exit(1) - def __call__(self, *args, **kwargs): + def _main_shell_completion( + self, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: t.Optional[str] = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + """ + if complete_var is None: + complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: """Alias for :meth:`main`.""" return self.main(*args, **kwargs) @@ -769,9 +1135,6 @@ class Command(BaseCommand): Click. A basic command handles command line parsing and might dispatch more parsing to commands nested below it. - .. versionchanged:: 2.0 - Added the `context_settings` parameter. - :param name: the name of the command to use unless a group overrides it. :param context_settings: an optional dictionary with defaults that are passed to the context object. @@ -785,108 +1148,178 @@ class Command(BaseCommand): shown on the command listing of the parent command. :param add_help_option: by default each command registers a ``--help`` option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed :param hidden: hide this command from help outputs. :param deprecated: issues a message indicating that the command is deprecated. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. """ - def __init__(self, name, context_settings=None, callback=None, - params=None, help=None, epilog=None, short_help=None, - options_metavar='[OPTIONS]', add_help_option=True, - hidden=False, deprecated=False): - BaseCommand.__init__(self, name, context_settings) + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + callback: t.Optional[t.Callable[..., t.Any]] = None, + params: t.Optional[t.List["Parameter"]] = None, + help: t.Optional[str] = None, + epilog: t.Optional[str] = None, + short_help: t.Optional[str] = None, + options_metavar: t.Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> None: + super().__init__(name, context_settings) #: the callback to execute when the command fires. This might be #: `None` in which case nothing happens. self.callback = callback #: the list of parameters for this command in the order they #: should show up in the help page and execute. Eager parameters #: will automatically be handled before non eager ones. - self.params = params or [] - # if a form feed (page break) is found in the help text, truncate help - # text to the content preceding the first form feed - if help and '\f' in help: - help = help.split('\f', 1)[0] + self.params: t.List["Parameter"] = params or [] self.help = help self.epilog = epilog self.options_metavar = options_metavar self.short_help = short_help self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help self.hidden = hidden self.deprecated = deprecated - def get_usage(self, ctx): + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + info_dict.update( + params=[param.to_info_dict() for param in self.get_params(ctx)], + help=self.help, + epilog=self.epilog, + short_help=self.short_help, + hidden=self.hidden, + deprecated=self.deprecated, + ) + return info_dict + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ formatter = ctx.make_formatter() self.format_usage(ctx, formatter) - return formatter.getvalue().rstrip('\n') + return formatter.getvalue().rstrip("\n") - def get_params(self, ctx): + def get_params(self, ctx: Context) -> t.List["Parameter"]: rv = self.params help_option = self.get_help_option(ctx) + if help_option is not None: - rv = rv + [help_option] + rv = [*rv, help_option] + return rv - def format_usage(self, ctx, formatter): - """Writes the usage line into the formatter.""" - pieces = self.collect_usage_pieces(ctx) - formatter.write_usage(ctx.command_path, ' '.join(pieces)) + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. - def collect_usage_pieces(self, ctx): + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: """Returns all the pieces that go into the usage line and returns it as a list of strings. """ - rv = [self.options_metavar] + rv = [self.options_metavar] if self.options_metavar else [] + for param in self.get_params(ctx): rv.extend(param.get_usage_pieces(ctx)) + return rv - def get_help_option_names(self, ctx): + def get_help_option_names(self, ctx: Context) -> t.List[str]: """Returns the names for the help option.""" all_names = set(ctx.help_option_names) for param in self.params: all_names.difference_update(param.opts) all_names.difference_update(param.secondary_opts) - return all_names + return list(all_names) - def get_help_option(self, ctx): + def get_help_option(self, ctx: Context) -> t.Optional["Option"]: """Returns the help option object.""" help_options = self.get_help_option_names(ctx) - if not help_options or not self.add_help_option: - return - def show_help(ctx, param, value): + if not help_options or not self.add_help_option: + return None + + def show_help(ctx: Context, param: "Parameter", value: str) -> None: if value and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) ctx.exit() - return Option(help_options, is_flag=True, - is_eager=True, expose_value=False, - callback=show_help, - help='Show this message and exit.') - def make_parser(self, ctx): + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help=_("Show this message and exit."), + ) + + def make_parser(self, ctx: Context) -> OptionParser: """Creates the underlying option parser for this command.""" parser = OptionParser(ctx) for param in self.get_params(ctx): param.add_to_parser(parser, ctx) return parser - def get_help(self, ctx): - """Formats the help into a string and returns it. This creates a - formatter and will call into the following formatting methods: + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. """ formatter = ctx.make_formatter() self.format_help(ctx, formatter) - return formatter.getvalue().rstrip('\n') + return formatter.getvalue().rstrip("\n") - def get_short_help_str(self, limit=45): - """Gets short help for the command or makes it by shortening the long help string.""" - return self.short_help or self.help and make_default_short_help(self.help, limit) or '' + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" - def format_help(self, ctx, formatter): + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the help into the formatter if it exists. - This calls into the following methods: + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: - :meth:`format_usage` - :meth:`format_help_text` @@ -898,21 +1331,21 @@ class Command(BaseCommand): self.format_options(ctx, formatter) self.format_epilog(ctx, formatter) - def format_help_text(self, ctx, formatter): + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the help text to the formatter if it exists.""" - if self.help: - formatter.write_paragraph() - with formatter.indentation(): - help_text = self.help - if self.deprecated: - help_text += DEPRECATED_HELP_NOTICE - formatter.write_text(help_text) - elif self.deprecated: - formatter.write_paragraph() - with formatter.indentation(): - formatter.write_text(DEPRECATED_HELP_NOTICE) + text = self.help if self.help is not None else "" - def format_options(self, ctx, formatter): + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + text = inspect.cleandoc(text).partition("\f")[0] + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes all the options into the formatter if they exist.""" opts = [] for param in self.get_params(ctx): @@ -921,40 +1354,90 @@ class Command(BaseCommand): opts.append(rv) if opts: - with formatter.section('Options'): + with formatter.section(_("Options")): formatter.write_dl(opts) - def format_epilog(self, ctx, formatter): + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the epilog into the formatter if it exists.""" if self.epilog: + epilog = inspect.cleandoc(self.epilog) formatter.write_paragraph() - with formatter.indentation(): - formatter.write_text(self.epilog) - def parse_args(self, ctx, args): + with formatter.indentation(): + formatter.write_text(epilog) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + parser = self.make_parser(ctx) opts, args, param_order = parser.parse_args(args=args) - for param in iter_params_for_processing( - param_order, self.get_params(ctx)): + for param in iter_params_for_processing(param_order, self.get_params(ctx)): value, args = param.handle_parse_result(ctx, opts, args) if args and not ctx.allow_extra_args and not ctx.resilient_parsing: - ctx.fail('Got unexpected extra argument%s (%s)' - % (len(args) != 1 and 's' or '', - ' '.join(map(make_str, args)))) + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) return args - def invoke(self, ctx): + def invoke(self, ctx: Context) -> t.Any: """Given a context, this invokes the attached callback (if it exists) in the right way. """ - _maybe_show_deprecated_notice(self) + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated." + ).format(name=self.name) + echo(style(message, fg="red"), err=True) + if self.callback is not None: return ctx.invoke(self.callback, **ctx.params) + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + results.extend(super().shell_complete(ctx, incomplete)) + return results + class MultiCommand(Command): """A multi command is the basic implementation of a command that @@ -976,48 +1459,81 @@ class MultiCommand(Command): is enabled. This restricts the form of commands in that they cannot have optional arguments but it allows multiple commands to be chained together. - :param result_callback: the result callback to attach to this multi - command. + :param result_callback: The result callback to attach to this multi + command. This can be set or changed later with the + :meth:`result_callback` decorator. """ + allow_extra_args = True allow_interspersed_args = False - def __init__(self, name=None, invoke_without_command=False, - no_args_is_help=None, subcommand_metavar=None, - chain=False, result_callback=None, **attrs): - Command.__init__(self, name, **attrs) + def __init__( + self, + name: t.Optional[str] = None, + invoke_without_command: bool = False, + no_args_is_help: t.Optional[bool] = None, + subcommand_metavar: t.Optional[str] = None, + chain: bool = False, + result_callback: t.Optional[t.Callable[..., t.Any]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + if no_args_is_help is None: no_args_is_help = not invoke_without_command + self.no_args_is_help = no_args_is_help self.invoke_without_command = invoke_without_command + if subcommand_metavar is None: if chain: - subcommand_metavar = SUBCOMMANDS_METAVAR + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." else: - subcommand_metavar = SUBCOMMAND_METAVAR + subcommand_metavar = "COMMAND [ARGS]..." + self.subcommand_metavar = subcommand_metavar self.chain = chain - #: The result callback that is stored. This can be set or - #: overridden with the :func:`resultcallback` decorator. - self.result_callback = result_callback + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback if self.chain: for param in self.params: if isinstance(param, Argument) and not param.required: - raise RuntimeError('Multi commands in chain mode cannot ' - 'have optional arguments.') + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) - def collect_usage_pieces(self, ctx): - rv = Command.collect_usage_pieces(self, ctx) + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + rv = super().collect_usage_pieces(ctx) rv.append(self.subcommand_metavar) return rv - def format_options(self, ctx, formatter): - Command.format_options(self, ctx, formatter) + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) self.format_commands(ctx, formatter) - def resultcallback(self, replace=False): - """Adds a result callback to the chain command. By default if a + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a result callback is already registered this will chain them but this can be disabled with the `replace` parameter. The result callback is invoked with the return value of the subcommand @@ -1032,28 +1548,36 @@ class MultiCommand(Command): def cli(input): return 42 - @cli.resultcallback() + @cli.result_callback() def process_result(result, input): return result + input - .. versionadded:: 3.0 - :param replace: if set to `True` an already existing result callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 """ - def decorator(f): - old_callback = self.result_callback + + def decorator(f: F) -> F: + old_callback = self._result_callback + if old_callback is None or replace: - self.result_callback = f + self._result_callback = f return f - def function(__value, *args, **kwargs): - return f(old_callback(__value, *args, **kwargs), - *args, **kwargs) - self.result_callback = rv = update_wrapper(function, f) + + def function(__value, *args, **kwargs): # type: ignore + inner = old_callback(__value, *args, **kwargs) # type: ignore + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) return rv + return decorator - def format_commands(self, ctx, formatter): + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: """Extra format methods for multi methods that adds all the commands after the options. """ @@ -1078,15 +1602,16 @@ class MultiCommand(Command): rows.append((subcommand, help)) if rows: - with formatter.section('Commands'): + with formatter.section(_("Commands")): formatter.write_dl(rows) - def parse_args(self, ctx, args): + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: if not args and self.no_args_is_help and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) ctx.exit() - rest = Command.parse_args(self, ctx, args) + rest = super().parse_args(ctx, args) + if self.chain: ctx.protected_args = rest ctx.args = [] @@ -1095,30 +1620,24 @@ class MultiCommand(Command): return ctx.args - def invoke(self, ctx): - def _process_result(value): - if self.result_callback is not None: - value = ctx.invoke(self.result_callback, value, - **ctx.params) + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) return value if not ctx.protected_args: - # If we are invoked without command the chain flag controls - # how this happens. If we are not in chain mode, the return - # value here is the return value of the command. - # If however we are in chain mode, the return value is the - # return value of the result processor invoked with an empty - # list (which means that no subcommand actually was executed). if self.invoke_without_command: - if not self.chain: - return Command.invoke(self, ctx) + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. with ctx: - Command.invoke(self, ctx) - return _process_result([]) - ctx.fail('Missing command.') + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) # Fetch args back out - args = ctx.protected_args + ctx.args + args = [*ctx.protected_args, *ctx.args] ctx.args = [] ctx.protected_args = [] @@ -1130,8 +1649,9 @@ class MultiCommand(Command): # resources until the result processor has worked. with ctx: cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None ctx.invoked_subcommand = cmd_name - Command.invoke(self, ctx) + super().invoke(ctx) sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) with sub_ctx: return _process_result(sub_ctx.command.invoke(sub_ctx)) @@ -1142,8 +1662,8 @@ class MultiCommand(Command): # set to ``*`` to inform the command that subcommands are executed # but nothing else. with ctx: - ctx.invoked_subcommand = args and '*' or None - Command.invoke(self, ctx) + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) # Otherwise we make every single context and invoke them in a # chain. In that case the return value to the result processor @@ -1151,9 +1671,14 @@ class MultiCommand(Command): contexts = [] while args: cmd_name, cmd, args = self.resolve_command(ctx, args) - sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, - allow_extra_args=True, - allow_interspersed_args=False) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) contexts.append(sub_ctx) args, sub_ctx.args = sub_ctx.args, [] @@ -1163,7 +1688,9 @@ class MultiCommand(Command): rv.append(sub_ctx.command.invoke(sub_ctx)) return _process_result(rv) - def resolve_command(self, ctx, args): + def resolve_command( + self, ctx: Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]: cmd_name = make_str(args[0]) original_cmd_name = cmd_name @@ -1185,73 +1712,212 @@ class MultiCommand(Command): if cmd is None and not ctx.resilient_parsing: if split_opt(cmd_name)[0]: self.parse_args(ctx, ctx.args) - ctx.fail('No such command "%s".' % original_cmd_name) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] - return cmd_name, cmd, args[1:] - - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: """Given a context and a command name, this returns a :class:`Command` object if it exists or returns `None`. """ - raise NotImplementedError() + raise NotImplementedError - def list_commands(self, ctx): + def list_commands(self, ctx: Context) -> t.List[str]: """Returns a list of subcommand names in the order they should appear. """ return [] + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + class Group(MultiCommand): - """A group allows a command to have subcommands attached. This is the - most common way to implement nesting in Click. + """A group allows a command to have subcommands attached. This is + the most common way to implement nesting in Click. - :param commands: a dictionary of commands. + :param name: The name of the group command. + :param commands: A dict mapping names to :class:`Command` objects. + Can also be a list of :class:`Command`, which will use + :attr:`Command.name` to create the dict. + :param attrs: Other command arguments described in + :class:`MultiCommand`, :class:`Command`, and + :class:`BaseCommand`. + + .. versionchanged:: 8.0 + The ``commmands`` argument can be a list of command objects. """ - def __init__(self, name=None, commands=None, **attrs): - MultiCommand.__init__(self, name, **attrs) - #: the registered subcommands by their exported names. - self.commands = commands or {} + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: t.Optional[t.Type[Command]] = None - def add_command(self, cmd, name=None): + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: t.Optional[str] = None, + commands: t.Optional[t.Union[t.Dict[str, Command], t.Sequence[Command]]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: t.Dict[str, Command] = commands + + def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: """Registers another :class:`Command` with this group. If the name is not provided, the name of the command is used. """ name = name or cmd.name if name is None: - raise TypeError('Command has no name.') + raise TypeError("Command has no name.") _check_multicommand(self, name, cmd, register=True) self.commands[name] = cmd - def command(self, *args, **kwargs): + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: + ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: """A shortcut decorator for declaring and attaching a command to - the group. This takes the same arguments as :func:`command` but - immediately registers the created command with this instance by - calling into :meth:`add_command`. + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. """ - def decorator(f): - cmd = command(*args, **kwargs)(f) + from .decorators import command + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'command(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) self.add_command(cmd) return cmd + + if func is not None: + return decorator(func) + return decorator - def group(self, *args, **kwargs): + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> "Group": + ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: """A shortcut decorator for declaring and attaching a group to - the group. This takes the same arguments as :func:`group` but - immediately registers the created command with this instance by - calling into :meth:`add_command`. + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. """ - def decorator(f): - cmd = group(*args, **kwargs)(f) + from .decorators import group + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'group(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> "Group": + cmd: Group = group(*args, **kwargs)(f) self.add_command(cmd) return cmd + + if func is not None: + return decorator(func) + return decorator - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: return self.commands.get(cmd_name) - def list_commands(self, ctx): + def list_commands(self, ctx: Context) -> t.List[str]: return sorted(self.commands) @@ -1262,31 +1928,52 @@ class CommandCollection(MultiCommand): provides all the commands for each of them. """ - def __init__(self, name=None, sources=None, **attrs): - MultiCommand.__init__(self, name, **attrs) + def __init__( + self, + name: t.Optional[str] = None, + sources: t.Optional[t.List[MultiCommand]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) #: The list of registered multi commands. - self.sources = sources or [] + self.sources: t.List[MultiCommand] = sources or [] - def add_source(self, multi_cmd): + def add_source(self, multi_cmd: MultiCommand) -> None: """Adds a new multi command to the chain dispatcher.""" self.sources.append(multi_cmd) - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: for source in self.sources: rv = source.get_command(ctx, cmd_name) + if rv is not None: if self.chain: _check_multicommand(self, cmd_name, rv) + return rv - def list_commands(self, ctx): - rv = set() + return None + + def list_commands(self, ctx: Context) -> t.List[str]: + rv: t.Set[str] = set() + for source in self.sources: rv.update(source.list_commands(ctx)) + return sorted(rv) -class Parameter(object): +def _check_iter(value: t.Any) -> t.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: r"""A parameter to a command comes in two versions: they are either :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently not supported by design as some of the internals for parsing are @@ -1294,12 +1981,6 @@ class Parameter(object): Some settings are supported by both options and arguments. - .. versionchanged:: 2.0 - Changed signature for parameter callback to also be passed the - parameter. In Click 2.0, the old callback format will still work, - but it will raise a warning to give you change to migrate the - code easier. - :param param_decls: the parameter declarations for this option or argument. This is a list of flags or argument names. @@ -1310,14 +1991,15 @@ class Parameter(object): :param default: the default value if omitted. This can also be a callable, in which case it's invoked when the default is needed without any arguments. - :param callback: a callback that should be executed after the parameter - was matched. This is called as ``fn(ctx, param, - value)`` and needs to return the value. Before Click - 2.0, the signature was ``(ctx, value)``. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. :param nargs: the number of arguments to match. If not ``1`` the return value is a tuple instead of single value. The default for nargs is ``1`` (except if the type is a tuple, then it's - the arity of the tuple). + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. :param metavar: how the value is represented in the help page. :param expose_value: if this is `True` then the value is passed onwards to the command callback and stored on the context, @@ -1327,17 +2009,70 @@ class Parameter(object): order of processing. :param envvar: a string or list of strings that are environment variables that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. """ - param_type_name = 'parameter' - def __init__(self, param_decls=None, type=None, required=False, - default=None, callback=None, nargs=None, metavar=None, - expose_value=True, is_eager=False, envvar=None, - autocompletion=None): - self.name, self.opts, self.secondary_opts = \ - self._parse_decls(param_decls or (), expose_value) + param_type_name = "parameter" - self.type = convert_type(type, default) + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + required: bool = False, + default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None, + callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None, + nargs: t.Optional[int] = None, + multiple: bool = False, + metavar: t.Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None, + shell_complete: t.Optional[ + t.Callable[ + [Context, "Parameter", str], + t.Union[t.List["CompletionItem"], t.List[str]], + ] + ] = None, + ) -> None: + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type = types.convert_type(type, default) # Default nargs to what the type tells us if we have that # information available. @@ -1350,151 +2085,325 @@ class Parameter(object): self.required = required self.callback = callback self.nargs = nargs - self.multiple = False + self.multiple = multiple self.expose_value = expose_value self.default = default self.is_eager = is_eager self.metavar = metavar self.envvar = envvar - self.autocompletion = autocompletion + self._custom_shell_complete = shell_complete + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + raise NotImplementedError() @property - def human_readable_name(self): + def human_readable_name(self) -> str: """Returns the human readable name of this parameter. This is the same as the name for options, but the metavar for arguments. """ - return self.name + return self.name # type: ignore - def make_metavar(self): + def make_metavar(self) -> str: if self.metavar is not None: return self.metavar + metavar = self.type.get_metavar(self) + if metavar is None: metavar = self.type.name.upper() + if self.nargs != 1: - metavar += '...' + metavar += "..." + return metavar - def get_default(self, ctx): - """Given a context variable this calculates the default value.""" - # Otherwise go with the regular default. - if callable(self.default): - rv = self.default() - else: - rv = self.default - return self.type_cast_value(ctx, rv) + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... - def add_to_parser(self, parser, ctx): - pass + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore - def consume_value(self, ctx, opts): - value = opts.get(self.name) if value is None: - value = self.value_from_envvar(ctx) - if value is None: - value = ctx.lookup_default(self.name) + value = self.default + + if call and callable(value): + value = value() + return value - def type_cast_value(self, ctx, value): - """Given a value this runs it properly through the type system. - This automatically handles things like `nargs` and `multiple` as - well as composite types. + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. """ - if self.type.is_composite: - if self.nargs <= 1: - raise TypeError('Attempted to invoke composite type ' - 'but nargs has been set to %s. This is ' - 'not supported; nargs needs to be set to ' - 'a fixed value > 1.' % self.nargs) - if self.multiple: - return tuple(self.type(x or (), self, ctx) for x in value or ()) - return self.type(value or (), self, ctx) + if value is None: + return () if self.multiple or self.nargs == -1 else None - def _convert(value, level): - if level == 0: - return self.type(value, self, ctx) - return tuple(_convert(x, level - 1) for x in value or ()) - return _convert(value, (self.nargs != 1) + bool(self.multiple)) + def check_iter(value: t.Any) -> t.Iterator: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None - def process_value(self, ctx, value): - """Given a value and context this runs the logic to convert the - value as necessary. - """ - # If the value we were given is None we do nothing. This way - # code that calls this can easily figure out if something was - # not provided. Otherwise it would be converted into an empty - # tuple for multiple invocations which is inconvenient. - if value is not None: - return self.type_cast_value(ctx, value) + if self.nargs == 1 or self.type.is_composite: + convert: t.Callable[[t.Any], t.Any] = partial( + self.type, param=self, ctx=ctx + ) + elif self.nargs == -1: - def value_is_missing(self, value): + def convert(value: t.Any) -> t.Tuple: + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Tuple: + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: if value is None: return True + if (self.nargs != 1 or self.multiple) and value == (): return True + return False - def full_process_value(self, ctx, value): - value = self.process_value(ctx, value) - - if value is None and not ctx.resilient_parsing: - value = self.get_default(ctx) + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) if self.required and self.value_is_missing(value): raise MissingParameter(ctx=ctx, param=self) + if self.callback is not None: + value = self.callback(ctx, self, value) + return value - def resolve_envvar_value(self, ctx): + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: if self.envvar is None: - return - if isinstance(self.envvar, (tuple, list)): + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: for envvar in self.envvar: rv = os.environ.get(envvar) - if rv is not None: - return rv - else: - return os.environ.get(self.envvar) - def value_from_envvar(self, ctx): - rv = self.resolve_envvar_value(ctx) + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + if rv is not None and self.nargs != 1: rv = self.type.split_envvar_value(rv) + return rv - def handle_parse_result(self, ctx, opts, args): + def handle_parse_result( + self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str] + ) -> t.Tuple[t.Any, t.List[str]]: with augment_usage_errors(ctx, param=self): - value = self.consume_value(ctx, opts) + value, source = self.consume_value(ctx, opts) + ctx.set_parameter_source(self.name, source) # type: ignore + try: - value = self.full_process_value(ctx, value) + value = self.process_value(ctx, value) except Exception: if not ctx.resilient_parsing: raise + value = None - if self.callback is not None: - try: - value = invoke_param_callback( - self.callback, ctx, self, value) - except Exception: - if not ctx.resilient_parsing: - raise if self.expose_value: - ctx.params[self.name] = value + ctx.params[self.name] = value # type: ignore + return value, args - def get_help_record(self, ctx): + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: pass - def get_usage_pieces(self, ctx): + def get_usage_pieces(self, ctx: Context) -> t.List[str]: return [] - def get_error_hint(self, ctx): + def get_error_hint(self, ctx: Context) -> str: """Get a stringified version of the param for use in error messages to indicate which param caused the error. """ hint_list = self.opts or [self.human_readable_name] - return ' / '.join('"%s"' % x for x in hint_list) + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast(t.List["CompletionItem"], results) + + return self.type.shell_complete(ctx, self, incomplete) class Option(Parameter): @@ -1503,21 +2412,27 @@ class Option(Parameter): All other parameters are passed onwards to the parameter constructor. - :param show_default: controls if the default value should be shown on the - help page. Normally, defaults are not shown. If this - value is a string, it shows the string instead of the - value. This is particularly useful for dynamic options. - :param show_envvar: controls if an environment variable should be shown on - the help page. Normally, environment variables - are not shown. - :param prompt: if set to `True` or a non empty string then the user will be - prompted for input. If set to `True` the prompt will be the - option name capitalized. - :param confirmation_prompt: if set then the value will need to be confirmed - if it was prompted for. - :param hide_input: if this is `True` then the input on the prompt will be - hidden from the user. This is useful for password - input. + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page. Normally, environment variables are not + shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. :param is_flag: forces this option to act as a flag. The default is auto detection. :param flag_value: which value should be used for this flag if it's @@ -1534,96 +2449,167 @@ class Option(Parameter): context. :param help: the help string. :param hidden: hide this option from help outputs. - """ - param_type_name = 'option' - def __init__(self, param_decls=None, show_default=False, - prompt=False, confirmation_prompt=False, - hide_input=False, is_flag=None, flag_value=None, - multiple=False, count=False, allow_from_autoenv=True, - type=None, help=None, hidden=False, show_choices=True, - show_envvar=False, **attrs): - default_is_missing = attrs.get('default', _missing) is _missing - Parameter.__init__(self, param_decls, type=type, **attrs) + .. versionchanged:: 8.1.0 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1.0 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1.0 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + show_default: t.Union[bool, str, None] = None, + prompt: t.Union[bool, str] = False, + confirmation_prompt: t.Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: t.Optional[bool] = None, + flag_value: t.Optional[t.Any] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + help: t.Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + default_is_missing = "default" not in attrs + super().__init__(param_decls, type=type, multiple=multiple, **attrs) if prompt is True: - prompt_text = self.name.replace('_', ' ').capitalize() + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize() elif prompt is False: prompt_text = None else: prompt_text = prompt + self.prompt = prompt_text self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required self.hide_input = hide_input self.hidden = hidden - # Flags + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + if is_flag is None: if flag_value is not None: + # Implicitly a flag because flag_value was set. is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False else: + # Implicitly a flag because flag options were given. is_flag = bool(self.secondary_opts) - if is_flag and default_is_missing: - self.default = False + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + + if is_flag and default_is_missing and not self.required: + self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False + if flag_value is None: flag_value = not self.default - self.is_flag = is_flag - self.flag_value = flag_value - if self.is_flag and isinstance(self.flag_value, bool) \ - and type is None: - self.type = BOOL - self.is_bool_flag = True - else: - self.is_bool_flag = False + + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value # Counting self.count = count if count: if type is None: - self.type = IntRange(min=0) + self.type = types.IntRange(min=0) if default_is_missing: self.default = 0 - self.multiple = multiple self.allow_from_autoenv = allow_from_autoenv self.help = help self.show_default = show_default self.show_choices = show_choices self.show_envvar = show_envvar - # Sanity check for stuff we don't support if __debug__: - if self.nargs < 0: - raise TypeError('Options cannot have nargs < 0') + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + if self.prompt and self.is_flag and not self.is_bool_flag: - raise TypeError('Cannot prompt for flags that are not bools.') + raise TypeError("'prompt' is not valid for non-boolean flag.") + if not self.is_bool_flag and self.secondary_opts: - raise TypeError('Got secondary option for non boolean flag.') - if self.is_bool_flag and self.hide_input \ - and self.prompt is not None: - raise TypeError('Hidden input does not work with boolean ' - 'flag prompts.') + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + if self.count: if self.multiple: - raise TypeError('Options cannot be multiple and count ' - 'at the same time.') - elif self.is_flag: - raise TypeError('Options cannot be count and flags at ' - 'the same time.') + raise TypeError("'count' is not valid with 'multiple'.") - def _parse_decls(self, decls, expose_value): + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + if self.multiple and self.is_flag: + raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: opts = [] secondary_opts = [] name = None possible_names = [] for decl in decls: - if isidentifier(decl): + if decl.isidentifier(): if name is not None: - raise TypeError('Name defined twice') + raise TypeError(f"Name '{name}' defined twice") name = decl else: - split_char = decl[:1] == '/' and ';' or '/' + split_char = ";" if decl[:1] == "/" else "/" if split_char in decl: first, second = decl.split(split_char, 1) first = first.rstrip() @@ -1633,123 +2619,218 @@ class Option(Parameter): second = second.lstrip() if second: secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) else: possible_names.append(split_opt(decl)) opts.append(decl) if name is None and possible_names: possible_names.sort(key=lambda x: -len(x[0])) # group long options first - name = possible_names[0][1].replace('-', '_').lower() - if not isidentifier(name): + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): name = None if name is None: if not expose_value: return None, opts, secondary_opts - raise TypeError('Could not determine name for option') + raise TypeError("Could not determine name for option") if not opts and not secondary_opts: - raise TypeError('No options defined but a name was passed (%s). ' - 'Did you mean to declare an argument instead ' - 'of an option?' % name) + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) return name, opts, secondary_opts - def add_to_parser(self, parser, ctx): - kwargs = { - 'dest': self.name, - 'nargs': self.nargs, - 'obj': self, - } - + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: if self.multiple: - action = 'append' + action = "append" elif self.count: - action = 'count' + action = "count" else: - action = 'store' + action = "store" if self.is_flag: - kwargs.pop('nargs', None) + action = f"{action}_const" + if self.is_bool_flag and self.secondary_opts: - parser.add_option(self.opts, action=action + '_const', - const=True, **kwargs) - parser.add_option(self.secondary_opts, action=action + - '_const', const=False, **kwargs) + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) else: - parser.add_option(self.opts, action=action + '_const', - const=self.flag_value, - **kwargs) + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) else: - kwargs['action'] = action - parser.add_option(self.opts, **kwargs) + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) - def get_help_record(self, ctx): + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: if self.hidden: - return - any_prefix_is_slash = [] + return None + + any_prefix_is_slash = False + + def _write_opts(opts: t.Sequence[str]) -> str: + nonlocal any_prefix_is_slash - def _write_opts(opts): rv, any_slashes = join_options(opts) + if any_slashes: - any_prefix_is_slash[:] = [True] + any_prefix_is_slash = True + if not self.is_flag and not self.count: - rv += ' ' + self.make_metavar() + rv += f" {self.make_metavar()}" + return rv rv = [_write_opts(self.opts)] + if self.secondary_opts: rv.append(_write_opts(self.secondary_opts)) - help = self.help or '' + help = self.help or "" extra = [] + if self.show_envvar: envvar = self.envvar + if envvar is None: - if self.allow_from_autoenv and \ - ctx.auto_envvar_prefix is not None: - envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + if envvar is not None: - extra.append('env var: %s' % ( - ', '.join('%s' % d for d in envvar) - if isinstance(envvar, (list, tuple)) - else envvar, )) - if self.default is not None and self.show_default: - if isinstance(self.show_default, string_types): - default_string = '({})'.format(self.show_default) - elif isinstance(self.default, (list, tuple)): - default_string = ', '.join('%s' % d for d in self.default) - elif inspect.isfunction(self.default): - default_string = "(dynamic)" + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True else: - default_string = self.default - extra.append('default: {}'.format(default_string)) + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or (show_default and (default_value is not None)): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = split_opt( + (self.opts if self.default else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + else: + default_string = str(default_value) + + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) if self.required: - extra.append('required') + extra.append(_("required")) + if extra: - help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra)) + extra_str = "; ".join(extra) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" - return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help) + return ("; " if any_prefix_is_slash else " / ").join(rv), help - def get_default(self, ctx): - # If we're a non boolean flag out default is more complex because + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + # If we're a non boolean flag our default is more complex because # we need to look at all flags in the same group to figure out - # if we're the the default one in which case we return the flag + # if we're the default one in which case we return the flag # value as default. if self.is_flag and not self.is_bool_flag: for param in ctx.command.params: if param.name == self.name and param.default: - return param.flag_value - return None - return Parameter.get_default(self, ctx) + return param.flag_value # type: ignore - def prompt_for_value(self, ctx): + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: """This is an alternative flow that can be activated in the full value processing if a value does not exist. It will prompt the user until a valid value exists and then returns the processed value as result. """ + assert self.prompt is not None + # Calculate the default before prompting anything to be stable. default = self.get_default(ctx) @@ -1758,36 +2839,87 @@ class Option(Parameter): if self.is_bool_flag: return confirm(self.prompt, default) - return prompt(self.prompt, default=default, type=self.type, - hide_input=self.hide_input, show_choices=self.show_choices, - confirmation_prompt=self.confirmation_prompt, - value_proc=lambda x: self.process_value(ctx, x)) + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + rv = super().resolve_envvar_value(ctx) - def resolve_envvar_value(self, ctx): - rv = Parameter.resolve_envvar_value(self, ctx) if rv is not None: return rv - if self.allow_from_autoenv and \ - ctx.auto_envvar_prefix is not None: - envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) - return os.environ.get(envvar) - def value_from_envvar(self, ctx): - rv = self.resolve_envvar_value(ctx) + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + if rv is None: return None + value_depth = (self.nargs != 1) + bool(self.multiple) - if value_depth > 0 and rv is not None: + + if value_depth > 0: rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: rv = batch(rv, self.nargs) + return rv - def full_process_value(self, ctx, value): - if value is None and self.prompt is not None \ - and not ctx.resilient_parsing: - return self.prompt_for_value(ctx) - return Parameter.full_process_value(self, ctx, value) + def consume_value( + self, ctx: Context, opts: t.Mapping[str, "Parameter"] + ) -> t.Tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source class Argument(Parameter): @@ -1797,60 +2929,70 @@ class Argument(Parameter): All parameters are passed onwards to the parameter constructor. """ - param_type_name = 'argument' - def __init__(self, param_decls, required=None, **attrs): + param_type_name = "argument" + + def __init__( + self, + param_decls: t.Sequence[str], + required: t.Optional[bool] = None, + **attrs: t.Any, + ) -> None: if required is None: - if attrs.get('default') is not None: + if attrs.get("default") is not None: required = False else: - required = attrs.get('nargs', 1) > 0 - Parameter.__init__(self, param_decls, required=required, **attrs) - if self.default is not None and self.nargs < 0: - raise TypeError('nargs=-1 in combination with a default value ' - 'is not supported.') + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") @property - def human_readable_name(self): + def human_readable_name(self) -> str: if self.metavar is not None: return self.metavar - return self.name.upper() + return self.name.upper() # type: ignore - def make_metavar(self): + def make_metavar(self) -> str: if self.metavar is not None: return self.metavar var = self.type.get_metavar(self) if not var: - var = self.name.upper() + var = self.name.upper() # type: ignore if not self.required: - var = '[%s]' % var + var = f"[{var}]" if self.nargs != 1: - var += '...' + var += "..." return var - def _parse_decls(self, decls, expose_value): + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: if not decls: if not expose_value: return None, [], [] - raise TypeError('Could not determine name for argument') + raise TypeError("Could not determine name for argument") if len(decls) == 1: name = arg = decls[0] - name = name.replace('-', '_').lower() + name = name.replace("-", "_").lower() else: - raise TypeError('Arguments take exactly one ' - 'parameter declaration, got %d' % len(decls)) + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}." + ) return name, [arg], [] - def get_usage_pieces(self, ctx): + def get_usage_pieces(self, ctx: Context) -> t.List[str]: return [self.make_metavar()] - def get_error_hint(self, ctx): - return '"%s"' % self.make_metavar() + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar()}'" - def add_to_parser(self, parser, ctx): - parser.add_argument(dest=self.name, nargs=self.nargs, - obj=self) - - -# Circular dependency between decorators and core -from .decorators import command, group + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/libs/common/click/decorators.py b/libs/common/click/decorators.py index c57c5308..28618dc5 100644 --- a/libs/common/click/decorators.py +++ b/libs/common/click/decorators.py @@ -1,34 +1,48 @@ -import sys import inspect - +import types +import typing as t from functools import update_wrapper +from gettext import gettext as _ -from ._compat import iteritems -from ._unicodefun import _check_for_unicode_literals -from .utils import echo +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter from .globals import get_current_context +from .utils import echo + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command]) -def pass_context(f): +def pass_context(f: F) -> F: """Marks a callback as wanting to receive the current context object as first argument. """ - def new_func(*args, **kwargs): + + def new_func(*args, **kwargs): # type: ignore return f(get_current_context(), *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) -def pass_obj(f): +def pass_obj(f: F) -> F: """Similar to :func:`pass_context`, but only pass the object on the context onwards (:attr:`Context.obj`). This is useful if that object represents the state of a nested system. """ - def new_func(*args, **kwargs): + + def new_func(*args, **kwargs): # type: ignore return f(get_current_context().obj, *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) -def make_pass_decorator(object_type, ensure=False): +def make_pass_decorator( + object_type: t.Type, ensure: bool = False +) -> "t.Callable[[F], F]": """Given an object type this creates a decorator that will work similar to :func:`pass_obj` but instead of passing the object of the current context, it will find the innermost context of type @@ -50,55 +64,106 @@ def make_pass_decorator(object_type, ensure=False): :param ensure: if set to `True`, a new object will be created and remembered on the context if it's not there yet. """ - def decorator(f): - def new_func(*args, **kwargs): + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore ctx = get_current_context() + if ensure: obj = ctx.ensure_object(object_type) else: obj = ctx.find_object(object_type) + if obj is None: - raise RuntimeError('Managed to invoke callback without a ' - 'context object of type %r existing' - % object_type.__name__) + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + return ctx.invoke(f, obj, *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) + return decorator -def _make_command(f, name, attrs, cls): - if isinstance(f, Command): - raise TypeError('Attempted to convert a callback into a ' - 'command twice.') - try: - params = f.__click_params__ - params.reverse() - del f.__click_params__ - except AttributeError: - params = [] - help = attrs.get('help') - if help is None: - help = inspect.getdoc(f) - if isinstance(help, bytes): - help = help.decode('utf-8') - else: - help = inspect.cleandoc(help) - attrs['help'] = help - _check_for_unicode_literals() - return cls(name=name or f.__name__.lower().replace('_', '-'), - callback=f, params=params, **attrs) +def pass_meta_key( + key: str, *, doc_description: t.Optional[str] = None +) -> "t.Callable[[F], F]": + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator -def command(name=None, cls=None, **attrs): +CmdType = t.TypeVar("CmdType", bound=Command) + + +@t.overload +def command( + __func: t.Callable[..., t.Any], +) -> Command: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[..., Command]: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + cls: t.Type[CmdType] = ..., + **attrs: t.Any, +) -> t.Callable[..., CmdType]: + ... + + +def command( + name: t.Union[str, t.Callable[..., t.Any], None] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Union[Command, t.Callable[..., Command]]: r"""Creates a new :class:`Command` and uses the decorated function as callback. This will also automatically attach all decorated :func:`option`\s and :func:`argument`\s as parameters to the command. - The name of the command defaults to the name of the function. If you - want to change that, you can pass the intended name as the first - argument. + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. Once decorated the function turns into a :class:`Command` instance that can be invoked as a command line utility or be attached to a @@ -108,35 +173,105 @@ def command(name=None, cls=None, **attrs): name with underscores replaced by dashes. :param cls: the command class to instantiate. This defaults to :class:`Command`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. """ + + func: t.Optional[t.Callable[..., t.Any]] = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + if cls is None: cls = Command - def decorator(f): - cmd = _make_command(f, name, attrs, cls) + + def decorator(f: t.Callable[..., t.Any]) -> Command: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + cmd = cls( # type: ignore[misc] + name=name or f.__name__.lower().replace("_", "-"), # type: ignore[arg-type] + callback=f, + params=params, + **attrs, + ) cmd.__doc__ = f.__doc__ return cmd + + if func is not None: + return decorator(func) + return decorator -def group(name=None, **attrs): +@t.overload +def group( + __func: t.Callable[..., t.Any], +) -> Group: + ... + + +@t.overload +def group( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[[F], Group]: + ... + + +def group( + name: t.Union[str, t.Callable[..., t.Any], None] = None, **attrs: t.Any +) -> t.Union[Group, t.Callable[[F], Group]]: """Creates a new :class:`Group` with a function as callback. This works otherwise the same as :func:`command` just that the `cls` parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. """ - attrs.setdefault('cls', Group) - return command(name, **attrs) + if attrs.get("cls") is None: + attrs["cls"] = Group + + if callable(name): + grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs)) + return grp(name) + + return t.cast(Group, command(name, **attrs)) -def _param_memo(f, param): +def _param_memo(f: FC, param: Parameter) -> None: if isinstance(f, Command): f.params.append(param) else: - if not hasattr(f, '__click_params__'): - f.__click_params__ = [] - f.__click_params__.append(param) + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore -def argument(*param_decls, **attrs): +def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: """Attaches an argument to the command. All positional arguments are passed as parameter declarations to :class:`Argument`; all keyword arguments are forwarded unchanged (except ``cls``). @@ -146,14 +281,16 @@ def argument(*param_decls, **attrs): :param cls: the argument class to instantiate. This defaults to :class:`Argument`. """ - def decorator(f): - ArgumentClass = attrs.pop('cls', Argument) + + def decorator(f: FC) -> FC: + ArgumentClass = attrs.pop("cls", None) or Argument _param_memo(f, ArgumentClass(param_decls, **attrs)) return f + return decorator -def option(*param_decls, **attrs): +def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: """Attaches an option to the command. All positional arguments are passed as parameter declarations to :class:`Option`; all keyword arguments are forwarded unchanged (except ``cls``). @@ -163,149 +300,198 @@ def option(*param_decls, **attrs): :param cls: the option class to instantiate. This defaults to :class:`Option`. """ - def decorator(f): + + def decorator(f: FC) -> FC: # Issue 926, copy attrs, so pre-defined options can re-use the same cls= option_attrs = attrs.copy() - - if 'help' in option_attrs: - option_attrs['help'] = inspect.cleandoc(option_attrs['help']) - OptionClass = option_attrs.pop('cls', Option) + OptionClass = option_attrs.pop("cls", None) or Option _param_memo(f, OptionClass(param_decls, **option_attrs)) return f + return decorator -def confirmation_option(*param_decls, **attrs): - """Shortcut for confirmation prompts that can be ignored by passing - ``--yes`` as parameter. +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. - This is equivalent to decorating a function with :func:`option` with - the following parameters:: - - def callback(ctx, param, value): - if not value: - ctx.abort() - - @click.command() - @click.option('--yes', is_flag=True, callback=callback, - expose_value=False, prompt='Do you want to continue?') - def dropdb(): - pass + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - def callback(ctx, param, value): - if not value: - ctx.abort() - attrs.setdefault('is_flag', True) - attrs.setdefault('callback', callback) - attrs.setdefault('expose_value', False) - attrs.setdefault('prompt', 'Do you want to continue?') - attrs.setdefault('help', 'Confirm the action without prompting.') - return option(*(param_decls or ('--yes',)), **attrs)(f) - return decorator + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) -def password_option(*param_decls, **attrs): - """Shortcut for password prompts. +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. - This is equivalent to decorating a function with :func:`option` with - the following parameters:: - - @click.command() - @click.option('--password', prompt=True, confirmation_prompt=True, - hide_input=True) - def changeadmin(password): - pass + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - attrs.setdefault('prompt', True) - attrs.setdefault('confirmation_prompt', True) - attrs.setdefault('hide_input', True) - return option(*(param_decls or ('--password',)), **attrs)(f) - return decorator + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) -def version_option(version=None, *param_decls, **attrs): - """Adds a ``--version`` option which immediately ends the program - printing out the version number. This is implemented as an eager - option that prints the version and exits the program in the callback. +def version_option( + version: t.Optional[str] = None, + *param_decls: str, + package_name: t.Optional[str] = None, + prog_name: t.Optional[str] = None, + message: t.Optional[str] = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. - :param version: the version number to show. If not provided Click - attempts an auto discovery via setuptools. - :param prog_name: the name of the program (defaults to autodetection) - :param message: custom message to show instead of the default - (``'%(prog)s, version %(version)s'``) - :param others: everything else is forwarded to :func:`option`. + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. On Python < 3.8, the ``importlib_metadata`` + backport must be installed. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. """ - if version is None: - if hasattr(sys, '_getframe'): - module = sys._getframe(1).f_globals.get('__name__') - else: - module = '' + if message is None: + message = _("%(prog)s, version %(version)s") - def decorator(f): - prog_name = attrs.pop('prog_name', None) - message = attrs.pop('message', '%(prog)s, version %(version)s') + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame - def callback(ctx, param, value): - if not value or ctx.resilient_parsing: - return - prog = prog_name - if prog is None: - prog = ctx.find_root().info_name - ver = version - if ver is None: - try: - import pkg_resources - except ImportError: - pass - else: - for dist in pkg_resources.working_set: - scripts = dist.get_entry_map().get('console_scripts') or {} - for script_name, entry_point in iteritems(scripts): - if entry_point.module_name == module: - ver = dist.version - break - if ver is None: - raise RuntimeError('Could not determine version') - echo(message % { - 'prog': prog, - 'version': ver, - }, color=ctx.color) - ctx.exit() + if f_globals is not None: + package_name = f_globals.get("__name__") - attrs.setdefault('is_flag', True) - attrs.setdefault('expose_value', False) - attrs.setdefault('is_eager', True) - attrs.setdefault('help', 'Show the version and exit.') - attrs['callback'] = callback - return option(*(param_decls or ('--version',)), **attrs)(f) - return decorator + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + metadata: t.Optional[types.ModuleType] + + try: + from importlib import metadata # type: ignore + except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore + + try: + version = metadata.version(package_name) # type: ignore + except metadata.PackageNotFoundError: # type: ignore + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + t.cast(str, message) + % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) -def help_option(*param_decls, **attrs): - """Adds a ``--help`` option which immediately ends the program - printing out the help page. This is usually unnecessary to add as - this is added by default to all commands unless suppressed. +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--help`` option which immediately prints the help page + and exits the program. - Like :func:`version_option`, this is implemented as eager option that - prints in the callback and exits. + This is usually unnecessary, as the ``--help`` option is added to + each command automatically unless ``add_help_option=False`` is + passed. - All arguments are forwarded to :func:`option`. + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - def callback(ctx, param, value): - if value and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit() - attrs.setdefault('is_flag', True) - attrs.setdefault('expose_value', False) - attrs.setdefault('help', 'Show this message and exit.') - attrs.setdefault('is_eager', True) - attrs['callback'] = callback - return option(*(param_decls or ('--help',)), **attrs)(f) - return decorator + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return -# Circular dependencies between core and decorators -from .core import Command, Group, Argument, Option + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) diff --git a/libs/common/click/exceptions.py b/libs/common/click/exceptions.py index 6fa17658..9e20b3eb 100644 --- a/libs/common/click/exceptions.py +++ b/libs/common/click/exceptions.py @@ -1,43 +1,46 @@ -from ._compat import PY2, filename_to_ui, get_text_stderr +import os +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr from .utils import echo +if t.TYPE_CHECKING: + from .core import Context + from .core import Parameter + + +def _join_param_hints( + param_hint: t.Optional[t.Union[t.Sequence[str], str]] +) -> t.Optional[str]: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) -def _join_param_hints(param_hint): - if isinstance(param_hint, (tuple, list)): - return ' / '.join('"%s"' % x for x in param_hint) return param_hint class ClickException(Exception): """An exception that Click can handle and show to the user.""" - #: The exit code for this exception + #: The exit code for this exception. exit_code = 1 - def __init__(self, message): - ctor_msg = message - if PY2: - if ctor_msg is not None: - ctor_msg = ctor_msg.encode('utf-8') - Exception.__init__(self, ctor_msg) + def __init__(self, message: str) -> None: + super().__init__(message) self.message = message - def format_message(self): + def format_message(self) -> str: return self.message - def __str__(self): + def __str__(self) -> str: return self.message - if PY2: - __unicode__ = __str__ - - def __str__(self): - return self.message.encode('utf-8') - - def show(self, file=None): + def show(self, file: t.Optional[t.IO] = None) -> None: if file is None: file = get_text_stderr() - echo('Error: %s' % self.format_message(), file=file) + + echo(_("Error: {message}").format(message=self.format_message()), file=file) class UsageError(ClickException): @@ -48,26 +51,35 @@ class UsageError(ClickException): :param ctx: optionally the context that caused this error. Click will fill in the context automatically in some situations. """ + exit_code = 2 - def __init__(self, message, ctx=None): - ClickException.__init__(self, message) + def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: + super().__init__(message) self.ctx = ctx - self.cmd = self.ctx and self.ctx.command or None + self.cmd = self.ctx.command if self.ctx else None - def show(self, file=None): + def show(self, file: t.Optional[t.IO] = None) -> None: if file is None: file = get_text_stderr() color = None - hint = '' - if (self.cmd is not None and - self.cmd.get_help_option(self.ctx) is not None): - hint = ('Try "%s %s" for help.\n' - % (self.ctx.command_path, self.ctx.help_option_names[0])) + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" if self.ctx is not None: color = self.ctx.color - echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color) - echo('Error: %s' % self.format_message(), file=file, color=color) + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) class BadParameter(UsageError): @@ -88,22 +100,28 @@ class BadParameter(UsageError): each item is quoted and separated. """ - def __init__(self, message, ctx=None, param=None, - param_hint=None): - UsageError.__init__(self, message, ctx) + def __init__( + self, + message: str, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + ) -> None: + super().__init__(message, ctx) self.param = param self.param_hint = param_hint - def format_message(self): + def format_message(self) -> str: if self.param_hint is not None: param_hint = self.param_hint elif self.param is not None: - param_hint = self.param.get_error_hint(self.ctx) + param_hint = self.param.get_error_hint(self.ctx) # type: ignore else: - return 'Invalid value: %s' % self.message - param_hint = _join_param_hints(param_hint) + return _("Invalid value: {message}").format(message=self.message) - return 'Invalid value for %s: %s' % (param_hint, self.message) + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) class MissingParameter(BadParameter): @@ -118,19 +136,27 @@ class MissingParameter(BadParameter): ``'option'`` or ``'argument'``. """ - def __init__(self, message=None, ctx=None, param=None, - param_hint=None, param_type=None): - BadParameter.__init__(self, message, ctx, param, param_hint) + def __init__( + self, + message: t.Optional[str] = None, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + param_type: t.Optional[str] = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) self.param_type = param_type - def format_message(self): + def format_message(self) -> str: if self.param_hint is not None: - param_hint = self.param_hint + param_hint: t.Optional[str] = self.param_hint elif self.param is not None: - param_hint = self.param.get_error_hint(self.ctx) + param_hint = self.param.get_error_hint(self.ctx) # type: ignore else: param_hint = None + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" param_type = self.param_type if param_type is None and self.param is not None: @@ -141,16 +167,30 @@ class MissingParameter(BadParameter): msg_extra = self.param.type.get_missing_message(self.param) if msg_extra: if msg: - msg += '. ' + msg_extra + msg += f". {msg_extra}" else: msg = msg_extra - return 'Missing %s%s%s%s' % ( - param_type, - param_hint and ' %s' % param_hint or '', - msg and '. ' or '.', - msg or '', - ) + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message class NoSuchOption(UsageError): @@ -160,23 +200,31 @@ class NoSuchOption(UsageError): .. versionadded:: 4.0 """ - def __init__(self, option_name, message=None, possibilities=None, - ctx=None): + def __init__( + self, + option_name: str, + message: t.Optional[str] = None, + possibilities: t.Optional[t.Sequence[str]] = None, + ctx: t.Optional["Context"] = None, + ) -> None: if message is None: - message = 'no such option: %s' % option_name - UsageError.__init__(self, message, ctx) + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) self.option_name = option_name self.possibilities = possibilities - def format_message(self): - bits = [self.message] - if self.possibilities: - if len(self.possibilities) == 1: - bits.append('Did you mean %s?' % self.possibilities[0]) - else: - possibilities = sorted(self.possibilities) - bits.append('(Possible options: %s)' % ', '.join(possibilities)) - return ' '.join(bits) + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" class BadOptionUsage(UsageError): @@ -189,8 +237,10 @@ class BadOptionUsage(UsageError): :param option_name: the name of the option being used incorrectly. """ - def __init__(self, option_name, message, ctx=None): - UsageError.__init__(self, message, ctx) + def __init__( + self, option_name: str, message: str, ctx: t.Optional["Context"] = None + ) -> None: + super().__init__(message, ctx) self.option_name = option_name @@ -202,23 +252,22 @@ class BadArgumentUsage(UsageError): .. versionadded:: 6.0 """ - def __init__(self, message, ctx=None): - UsageError.__init__(self, message, ctx) - class FileError(ClickException): """Raised if a file cannot be opened.""" - def __init__(self, filename, hint=None): - ui_filename = filename_to_ui(filename) + def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: if hint is None: - hint = 'unknown error' - ClickException.__init__(self, hint) - self.ui_filename = ui_filename + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename = os.fsdecode(filename) self.filename = filename - def format_message(self): - return 'Could not open file %s: %s' % (self.ui_filename, self.message) + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) class Abort(RuntimeError): @@ -231,5 +280,8 @@ class Exit(RuntimeError): :param code: the status code to exit with. """ - def __init__(self, code=0): + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: self.exit_code = code diff --git a/libs/common/click/formatting.py b/libs/common/click/formatting.py index a3d6a4d3..ddd2a2f8 100644 --- a/libs/common/click/formatting.py +++ b/libs/common/click/formatting.py @@ -1,29 +1,38 @@ +import typing as t from contextlib import contextmanager -from .termui import get_terminal_size -from .parser import split_opt -from ._compat import term_len +from gettext import gettext as _ +from ._compat import term_len +from .parser import split_opt # Can force a width. This is used by the test system -FORCED_WIDTH = None +FORCED_WIDTH: t.Optional[int] = None -def measure_table(rows): - widths = {} +def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: + widths: t.Dict[int, int] = {} + for row in rows: for idx, col in enumerate(row): widths[idx] = max(widths.get(idx, 0), term_len(col)) + return tuple(y for x, y in sorted(widths.items())) -def iter_rows(rows, col_count): +def iter_rows( + rows: t.Iterable[t.Tuple[str, str]], col_count: int +) -> t.Iterator[t.Tuple[str, ...]]: for row in rows: - row = tuple(row) - yield row + ('',) * (col_count - len(row)) + yield row + ("",) * (col_count - len(row)) -def wrap_text(text, width=78, initial_indent='', subsequent_indent='', - preserve_paragraphs=False): +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: """A helper function that intelligently wraps text. By default, it assumes that it operates on a single paragraph of text but if the `preserve_paragraphs` parameter is provided it will intelligently @@ -43,24 +52,28 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='', intelligently handle paragraphs. """ from ._textwrap import TextWrapper + text = text.expandtabs() - wrapper = TextWrapper(width, initial_indent=initial_indent, - subsequent_indent=subsequent_indent, - replace_whitespace=False) + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) if not preserve_paragraphs: return wrapper.fill(text) - p = [] - buf = [] + p: t.List[t.Tuple[int, bool, str]] = [] + buf: t.List[str] = [] indent = None - def _flush_par(): + def _flush_par() -> None: if not buf: return - if buf[0].strip() == '\b': - p.append((indent or 0, True, '\n'.join(buf[1:]))) + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) else: - p.append((indent or 0, False, ' '.join(buf))) + p.append((indent or 0, False, " ".join(buf))) del buf[:] for line in text.splitlines(): @@ -77,16 +90,16 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='', rv = [] for indent, raw, text in p: - with wrapper.extra_indent(' ' * indent): + with wrapper.extra_indent(" " * indent): if raw: rv.append(wrapper.indent_only(text)) else: rv.append(wrapper.fill(text)) - return '\n\n'.join(rv) + return "\n\n".join(rv) -class HelpFormatter(object): +class HelpFormatter: """This class helps with formatting text-based help pages. It's usually just needed for very special internal cases, but it's also exposed so that developers can write their own fancy outputs. @@ -98,79 +111,108 @@ class HelpFormatter(object): width clamped to a maximum of 78. """ - def __init__(self, indent_increment=2, width=None, max_width=None): + def __init__( + self, + indent_increment: int = 2, + width: t.Optional[int] = None, + max_width: t.Optional[int] = None, + ) -> None: + import shutil + self.indent_increment = indent_increment if max_width is None: max_width = 80 if width is None: width = FORCED_WIDTH if width is None: - width = max(min(get_terminal_size()[0], max_width) - 2, 50) + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) self.width = width self.current_indent = 0 - self.buffer = [] + self.buffer: t.List[str] = [] - def write(self, string): + def write(self, string: str) -> None: """Writes a unicode string into the internal buffer.""" self.buffer.append(string) - def indent(self): + def indent(self) -> None: """Increases the indentation.""" self.current_indent += self.indent_increment - def dedent(self): + def dedent(self) -> None: """Decreases the indentation.""" self.current_indent -= self.indent_increment - def write_usage(self, prog, args='', prefix='Usage: '): + def write_usage( + self, prog: str, args: str = "", prefix: t.Optional[str] = None + ) -> None: """Writes a usage line into the buffer. :param prog: the program name. :param args: whitespace separated list of arguments. - :param prefix: the prefix for the first line. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. """ - usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog) + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " text_width = self.width - self.current_indent if text_width >= (term_len(usage_prefix) + 20): # The arguments will fit to the right of the prefix. - indent = ' ' * term_len(usage_prefix) - self.write(wrap_text(args, text_width, - initial_indent=usage_prefix, - subsequent_indent=indent)) + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) else: # The prefix is too long, put the arguments on the next line. self.write(usage_prefix) - self.write('\n') - indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4) - self.write(wrap_text(args, text_width, - initial_indent=indent, - subsequent_indent=indent)) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) - self.write('\n') + self.write("\n") - def write_heading(self, heading): + def write_heading(self, heading: str) -> None: """Writes a heading into the buffer.""" - self.write('%*s%s:\n' % (self.current_indent, '', heading)) + self.write(f"{'':>{self.current_indent}}{heading}:\n") - def write_paragraph(self): + def write_paragraph(self) -> None: """Writes a paragraph into the buffer.""" if self.buffer: - self.write('\n') + self.write("\n") - def write_text(self, text): + def write_text(self, text: str) -> None: """Writes re-indented text into the buffer. This rewraps and preserves paragraphs. """ - text_width = max(self.width - self.current_indent, 11) - indent = ' ' * self.current_indent - self.write(wrap_text(text, text_width, - initial_indent=indent, - subsequent_indent=indent, - preserve_paragraphs=True)) - self.write('\n') + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") - def write_dl(self, rows, col_max=30, col_spacing=2): + def write_dl( + self, + rows: t.Sequence[t.Tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: """Writes a definition list into the buffer. This is how options and commands are usually formatted. @@ -182,33 +224,35 @@ class HelpFormatter(object): rows = list(rows) widths = measure_table(rows) if len(widths) != 2: - raise TypeError('Expected two columns for definition list') + raise TypeError("Expected two columns for definition list") first_col = min(widths[0], col_max) + col_spacing for first, second in iter_rows(rows, len(widths)): - self.write('%*s%s' % (self.current_indent, '', first)) + self.write(f"{'':>{self.current_indent}}{first}") if not second: - self.write('\n') + self.write("\n") continue if term_len(first) <= first_col - col_spacing: - self.write(' ' * (first_col - term_len(first))) + self.write(" " * (first_col - term_len(first))) else: - self.write('\n') - self.write(' ' * (first_col + self.current_indent)) + self.write("\n") + self.write(" " * (first_col + self.current_indent)) text_width = max(self.width - first_col - 2, 10) - lines = iter(wrap_text(second, text_width).splitlines()) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + if lines: - self.write(next(lines) + '\n') - for line in lines: - self.write('%*s%s\n' % ( - first_col + self.current_indent, '', line)) + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") else: - self.write('\n') + self.write("\n") @contextmanager - def section(self, name): + def section(self, name: str) -> t.Iterator[None]: """Helpful context manager that writes a paragraph, a heading, and the indents. @@ -223,7 +267,7 @@ class HelpFormatter(object): self.dedent() @contextmanager - def indentation(self): + def indentation(self) -> t.Iterator[None]: """A context manager that increases the indentation.""" self.indent() try: @@ -231,12 +275,12 @@ class HelpFormatter(object): finally: self.dedent() - def getvalue(self): + def getvalue(self) -> str: """Returns the buffer contents.""" - return ''.join(self.buffer) + return "".join(self.buffer) -def join_options(options): +def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]: """Given a list of option strings this joins them in the most appropriate way and returns them in the form ``(formatted_string, any_prefix_is_slash)`` where the second item in the tuple is a flag that @@ -244,13 +288,14 @@ def join_options(options): """ rv = [] any_prefix_is_slash = False + for opt in options: prefix = split_opt(opt)[0] - if prefix == '/': + + if prefix == "/": any_prefix_is_slash = True + rv.append((len(prefix), opt)) rv.sort(key=lambda x: x[0]) - - rv = ', '.join(x[1] for x in rv) - return rv, any_prefix_is_slash + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/libs/common/click/globals.py b/libs/common/click/globals.py index 843b594a..480058f1 100644 --- a/libs/common/click/globals.py +++ b/libs/common/click/globals.py @@ -1,10 +1,24 @@ +import typing as t from threading import local +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context _local = local() -def get_current_context(silent=False): +@t.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@t.overload +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: + ... + + +def get_current_context(silent: bool = False) -> t.Optional["Context"]: """Returns the current click context. This can be used as a way to access the current context object from anywhere. This is a more implicit alternative to the :func:`pass_context` decorator. This function is @@ -15,34 +29,40 @@ def get_current_context(silent=False): .. versionadded:: 5.0 - :param silent: is set to `True` the return value is `None` if no context + :param silent: if set to `True` the return value is `None` if no context is available. The default behavior is to raise a :exc:`RuntimeError`. """ try: - return getattr(_local, 'stack')[-1] - except (AttributeError, IndexError): + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: if not silent: - raise RuntimeError('There is no active click context.') + raise RuntimeError("There is no active click context.") from e + + return None -def push_context(ctx): +def push_context(ctx: "Context") -> None: """Pushes a new context to the current stack.""" - _local.__dict__.setdefault('stack', []).append(ctx) + _local.__dict__.setdefault("stack", []).append(ctx) -def pop_context(): +def pop_context() -> None: """Removes the top level from the stack.""" _local.stack.pop() -def resolve_color_default(color=None): - """"Internal helper to get the default value of the color flag. If a +def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]: + """Internal helper to get the default value of the color flag. If a value is passed it's returned unchanged, otherwise it's looked up from the current context. """ if color is not None: return color + ctx = get_current_context(silent=True) + if ctx is not None: return ctx.color + + return None diff --git a/libs/common/click/parser.py b/libs/common/click/parser.py index 1c3ae9c8..2d5a2ed7 100644 --- a/libs/common/click/parser.py +++ b/libs/common/click/parser.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- """ -click.parser -~~~~~~~~~~~~ - This module started out as largely a copy paste from the stdlib's optparse module with the features removed that we do not need from optparse because we implement them in Click on a higher level (for @@ -14,15 +10,45 @@ The reason this is a different module and not optparse from the stdlib is that there are differences in 2.x and 3.x about the error messages generated and optparse in the stdlib uses gettext for no good reason and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. """ - -import re +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +import typing as t from collections import deque -from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ - BadArgumentUsage +from gettext import gettext as _ +from gettext import ngettext + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() -def _unpack_args(args, nargs_spec): +def _unpack_args( + args: t.Sequence[str], nargs_spec: t.Sequence[int] +) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: """Given an iterable of arguments and an iterable of nargs specifications, it returns a tuple with all the unpacked arguments at the first index and all remaining arguments as the second. @@ -34,10 +60,10 @@ def _unpack_args(args, nargs_spec): """ args = deque(args) nargs_spec = deque(nargs_spec) - rv = [] - spos = None + rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] + spos: t.Optional[int] = None - def _fetch(c): + def _fetch(c: "te.Deque[V]") -> t.Optional[V]: try: if spos is None: return c.popleft() @@ -48,18 +74,25 @@ def _unpack_args(args, nargs_spec): while nargs_spec: nargs = _fetch(nargs_spec) + + if nargs is None: + continue + if nargs == 1: rv.append(_fetch(args)) elif nargs > 1: x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, # so we need to turn them around. if spos is not None: x.reverse() + rv.append(tuple(x)) elif nargs < 0: if spos is not None: - raise TypeError('Cannot have two nargs < 0') + raise TypeError("Cannot have two nargs < 0") + spos = len(rv) rv.append(None) @@ -68,54 +101,71 @@ def _unpack_args(args, nargs_spec): if spos is not None: rv[spos] = tuple(args) args = [] - rv[spos + 1:] = reversed(rv[spos + 1:]) + rv[spos + 1 :] = reversed(rv[spos + 1 :]) return tuple(rv), list(args) -def _error_opt_args(nargs, opt): - if nargs == 1: - raise BadOptionUsage(opt, '%s option requires an argument' % opt) - raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs)) - - -def split_opt(opt): +def split_opt(opt: str) -> t.Tuple[str, str]: first = opt[:1] if first.isalnum(): - return '', opt + return "", opt if opt[1:2] == first: return opt[:2], opt[2:] return first, opt[1:] -def normalize_opt(opt, ctx): +def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: if ctx is None or ctx.token_normalize_func is None: return opt prefix, opt = split_opt(opt) - return prefix + ctx.token_normalize_func(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" -def split_arg_string(string): - """Given an argument string this attempts to split it into small parts.""" - rv = [] - for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'" - r'|"([^"\\]*(?:\\.[^"\\]*)*)"' - r'|\S+)\s*', string, re.S): - arg = match.group().strip() - if arg[:1] == arg[-1:] and arg[:1] in '"\'': - arg = arg[1:-1].encode('ascii', 'backslashreplace') \ - .decode('unicode-escape') - try: - arg = type(string)(arg) - except UnicodeError: - pass - rv.append(arg) - return rv +def split_arg_string(string: str) -> t.List[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out -class Option(object): - - def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): +class Option: + def __init__( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ): self._short_opts = [] self._long_opts = [] self.prefixes = set() @@ -123,8 +173,7 @@ class Option(object): for opt in opts: prefix, value = split_opt(opt) if not prefix: - raise ValueError('Invalid start character for option (%s)' - % opt) + raise ValueError(f"Invalid start character for option ({opt})") self.prefixes.add(prefix[0]) if len(prefix) == 1 and len(value) == 1: self._short_opts.append(opt) @@ -133,7 +182,7 @@ class Option(object): self.prefixes.add(prefix) if action is None: - action = 'store' + action = "store" self.dest = dest self.action = action @@ -142,54 +191,66 @@ class Option(object): self.obj = obj @property - def takes_value(self): - return self.action in ('store', 'append') + def takes_value(self) -> bool: + return self.action in ("store", "append") - def process(self, value, state): - if self.action == 'store': - state.opts[self.dest] = value - elif self.action == 'store_const': - state.opts[self.dest] = self.const - elif self.action == 'append': - state.opts.setdefault(self.dest, []).append(value) - elif self.action == 'append_const': - state.opts.setdefault(self.dest, []).append(self.const) - elif self.action == 'count': - state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 + def process(self, value: str, state: "ParsingState") -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore else: - raise ValueError('unknown action %r' % self.action) + raise ValueError(f"unknown action '{self.action}'") state.order.append(self.obj) -class Argument(object): - - def __init__(self, dest, nargs=1, obj=None): +class Argument: + def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): self.dest = dest self.nargs = nargs self.obj = obj - def process(self, value, state): + def process( + self, + value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], + state: "ParsingState", + ) -> None: if self.nargs > 1: + assert value is not None holes = sum(1 for x in value if x is None) if holes == len(value): value = None elif holes != 0: - raise BadArgumentUsage('argument %s takes %d values' - % (self.dest, self.nargs)) - state.opts[self.dest] = value + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore state.order.append(self.obj) -class ParsingState(object): - - def __init__(self, rargs): - self.opts = {} - self.largs = [] +class ParsingState: + def __init__(self, rargs: t.List[str]) -> None: + self.opts: t.Dict[str, t.Any] = {} + self.largs: t.List[str] = [] self.rargs = rargs - self.order = [] + self.order: t.List["CoreParameter"] = [] -class OptionParser(object): +class OptionParser: """The option parser is an internal class that is ultimately used to parse options and arguments. It's modelled after optparse and brings a similar but vastly simplified API. It should generally not be used @@ -203,7 +264,7 @@ class OptionParser(object): should go with. """ - def __init__(self, ctx=None): + def __init__(self, ctx: t.Optional["Context"] = None) -> None: #: The :class:`~click.Context` for this parser. This might be #: `None` for some advanced use cases. self.ctx = ctx @@ -217,46 +278,54 @@ class OptionParser(object): #: second mode where it will ignore it and continue processing #: after shifting all the unknown options into the resulting args. self.ignore_unknown_options = False + if ctx is not None: self.allow_interspersed_args = ctx.allow_interspersed_args self.ignore_unknown_options = ctx.ignore_unknown_options - self._short_opt = {} - self._long_opt = {} - self._opt_prefixes = set(['-', '--']) - self._args = [] - def add_option(self, opts, dest, action=None, nargs=1, const=None, - obj=None): + self._short_opt: t.Dict[str, Option] = {} + self._long_opt: t.Dict[str, Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: t.List[Argument] = [] + + def add_option( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ) -> None: """Adds a new option named `dest` to the parser. The destination is not inferred (unlike with optparse) and needs to be explicitly provided. Action can be any of ``store``, ``store_const``, - ``append``, ``appnd_const`` or ``count``. + ``append``, ``append_const`` or ``count``. The `obj` can be used to identify the option in the order list that is returned from the parser. """ - if obj is None: - obj = dest opts = [normalize_opt(opt, self.ctx) for opt in opts] - option = Option(opts, dest, action=action, nargs=nargs, - const=const, obj=obj) + option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) self._opt_prefixes.update(option.prefixes) for opt in option._short_opts: self._short_opt[opt] = option for opt in option._long_opts: self._long_opt[opt] = option - def add_argument(self, dest, nargs=1, obj=None): + def add_argument( + self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 + ) -> None: """Adds a positional argument named `dest` to the parser. The `obj` can be used to identify the option in the order list that is returned from the parser. """ - if obj is None: - obj = dest - self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) + self._args.append(Argument(obj, dest=dest, nargs=nargs)) - def parse_args(self, args): + def parse_args( + self, args: t.List[str] + ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: """Parses positional arguments and returns ``(values, args, order)`` for the parsed options and arguments as well as the leftover arguments if there are any. The order is a list of objects as they @@ -272,9 +341,10 @@ class OptionParser(object): raise return state.opts, state.largs, state.order - def _process_args_for_args(self, state): - pargs, args = _unpack_args(state.largs + state.rargs, - [x.nargs for x in self._args]) + def _process_args_for_args(self, state: ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) for idx, arg in enumerate(self._args): arg.process(pargs[idx], state) @@ -282,13 +352,13 @@ class OptionParser(object): state.largs = args state.rargs = [] - def _process_args_for_options(self, state): + def _process_args_for_options(self, state: ParsingState) -> None: while state.rargs: arg = state.rargs.pop(0) arglen = len(arg) # Double dashes always handled explicitly regardless of what # prefixes are valid. - if arg == '--': + if arg == "--": return elif arg[:1] in self._opt_prefixes and arglen > 1: self._process_opts(arg, state) @@ -318,10 +388,13 @@ class OptionParser(object): # *empty* -- still a subset of [arg0, ..., arg(i-1)], but # not a very interesting subset! - def _match_long_opt(self, opt, explicit_value, state): + def _match_long_opt( + self, opt: str, explicit_value: t.Optional[str], state: ParsingState + ) -> None: if opt not in self._long_opt: - possibilities = [word for word in self._long_opt - if word.startswith(opt)] + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) option = self._long_opt[opt] @@ -333,31 +406,26 @@ class OptionParser(object): if explicit_value is not None: state.rargs.insert(0, explicit_value) - nargs = option.nargs - if len(state.rargs) < nargs: - _error_opt_args(nargs, opt) - elif nargs == 1: - value = state.rargs.pop(0) - else: - value = tuple(state.rargs[:nargs]) - del state.rargs[:nargs] + value = self._get_value_from_state(opt, option, state) elif explicit_value is not None: - raise BadOptionUsage(opt, '%s option does not take a value' % opt) + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) else: value = None option.process(value, state) - def _match_short_opt(self, arg, state): + def _match_short_opt(self, arg: str, state: ParsingState) -> None: stop = False i = 1 prefix = arg[0] unknown_options = [] for ch in arg[1:]: - opt = normalize_opt(prefix + ch, self.ctx) + opt = normalize_opt(f"{prefix}{ch}", self.ctx) option = self._short_opt.get(opt) i += 1 @@ -373,14 +441,7 @@ class OptionParser(object): state.rargs.insert(0, arg[i:]) stop = True - nargs = option.nargs - if len(state.rargs) < nargs: - _error_opt_args(nargs, opt) - elif nargs == 1: - value = state.rargs.pop(0) - else: - value = tuple(state.rargs[:nargs]) - del state.rargs[:nargs] + value = self._get_value_from_state(opt, option, state) else: value = None @@ -395,15 +456,53 @@ class OptionParser(object): # to the state as new larg. This way there is basic combinatorics # that can be achieved while still ignoring unknown arguments. if self.ignore_unknown_options and unknown_options: - state.largs.append(prefix + ''.join(unknown_options)) + state.largs.append(f"{prefix}{''.join(unknown_options)}") - def _process_opts(self, arg, state): + def _get_value_from_state( + self, option_name: str, option: Option, state: ParsingState + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: ParsingState) -> None: explicit_value = None # Long option handling happens in two parts. The first part is # supporting explicitly attached values. In any case, we will try # to long match the option first. - if '=' in arg: - long_opt, explicit_value = arg.split('=', 1) + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) else: long_opt = arg norm_long_opt = normalize_opt(long_opt, self.ctx) @@ -421,7 +520,10 @@ class OptionParser(object): # short option code and will instead raise the no option # error. if arg[:2] not in self._opt_prefixes: - return self._match_short_opt(arg, state) + self._match_short_opt(arg, state) + return + if not self.ignore_unknown_options: raise + state.largs.append(arg) diff --git a/libs/common/click/py.typed b/libs/common/click/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/libs/common/click/shell_completion.py b/libs/common/click/shell_completion.py new file mode 100644 index 00000000..c17a8e64 --- /dev/null +++ b/libs/common/click/shell_completion.py @@ -0,0 +1,580 @@ +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import BaseCommand +from .core import Context +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .parser import split_arg_string +from .utils import echo + + +def shell_complete( + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: t.Optional[str] = None, + **kwargs: t.Any, + ) -> None: + self.value = value + self.type = type + self.help = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +compdef %(complete_func)s %(prog_name)s; +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response; + + for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + set response $response $value; + end; + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> t.Dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: t.List[str], incomplete: str + ) -> t.List[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + def _check_version(self) -> None: + import subprocess + + output = subprocess.run( + ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + raise RuntimeError( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ) + ) + else: + raise RuntimeError( + _("Couldn't detect Bash version, shell completion is not supported.") + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +_available_shells: t.Dict[str, t.Type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: t.Type[ShellComplete], name: t.Optional[str] = None +) -> None: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + +def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + value = ctx.params[param.name] + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str] +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) + args = ctx.protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, MultiCommand): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + sub_ctx = cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx.protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: t.List[str], incomplete: str +) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/libs/common/click/termui.py b/libs/common/click/termui.py index bf9a3aa1..bfb2f5ae 100644 --- a/libs/common/click/termui.py +++ b/libs/common/click/termui.py @@ -1,81 +1,109 @@ +import inspect +import io +import itertools import os import sys -import struct -import inspect -import itertools +import typing as t +from gettext import gettext as _ -from ._compat import raw_input, text_type, string_types, \ - isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN -from .utils import echo -from .exceptions import Abort, UsageError -from .types import convert_type, Choice, Path +from ._compat import isatty +from ._compat import strip_ansi +from ._compat import WIN +from .exceptions import Abort +from .exceptions import UsageError from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") # The prompt functions to use. The doc tools currently override these # functions to customize how they work. -visible_prompt_func = raw_input +visible_prompt_func: t.Callable[[str], str] = input _ansi_colors = { - 'black': 30, - 'red': 31, - 'green': 32, - 'yellow': 33, - 'blue': 34, - 'magenta': 35, - 'cyan': 36, - 'white': 37, - 'reset': 39, - 'bright_black': 90, - 'bright_red': 91, - 'bright_green': 92, - 'bright_yellow': 93, - 'bright_blue': 94, - 'bright_magenta': 95, - 'bright_cyan': 96, - 'bright_white': 97, + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, } -_ansi_reset_all = '\033[0m' +_ansi_reset_all = "\033[0m" -def hidden_prompt_func(prompt): +def hidden_prompt_func(prompt: str) -> str: import getpass + return getpass.getpass(prompt) -def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None): +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Optional[t.Any] = None, + show_choices: bool = True, + type: t.Optional[ParamType] = None, +) -> str: prompt = text if type is not None and show_choices and isinstance(type, Choice): - prompt += ' (' + ", ".join(map(str, type.choices)) + ')' + prompt += f" ({', '.join(map(str, type.choices))})" if default is not None and show_default: - prompt = '%s [%s]' % (prompt, default) - return prompt + suffix + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" -def prompt(text, default=None, hide_input=False, confirmation_prompt=False, - type=None, value_proc=None, prompt_suffix=': ', show_default=True, - err=False, show_choices=True): +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name # type: ignore + + return default + + +def prompt( + text: str, + default: t.Optional[t.Any] = None, + hide_input: bool = False, + confirmation_prompt: t.Union[bool, str] = False, + type: t.Optional[t.Union[ParamType, t.Any]] = None, + value_proc: t.Optional[t.Callable[[str], t.Any]] = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: """Prompts a user for input. This is a convenience function that can be used to prompt a user for input later. - If the user aborts the input by sending a interrupt signal, this + If the user aborts the input by sending an interrupt signal, this function will catch it and raise a :exc:`Abort` exception. - .. versionadded:: 7.0 - Added the show_choices parameter. - - .. versionadded:: 6.0 - Added unicode support for cmd.exe on Windows. - - .. versionadded:: 4.0 - Added the `err` parameter. - :param text: the text to show for the prompt. :param default: the default value to use if no input happens. If this is not given it will prompt until it's aborted. :param hide_input: if this is set to true then the input value will be hidden. - :param confirmation_prompt: asks for confirmation for the value. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. :param type: the type to use to check the value against. :param value_proc: if this parameter is provided it's a function that is invoked instead of the type conversion to @@ -88,93 +116,133 @@ def prompt(text, default=None, hide_input=False, confirmation_prompt=False, For example if type is a Choice of either day or week, show_choices is true and text is "Group by" then the prompt will be "Group by (day, week): ". - """ - result = None - def prompt_func(text): - f = hide_input and hidden_prompt_func or visible_prompt_func + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(text, nl=False, err=err) - return f('') + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") except (KeyboardInterrupt, EOFError): # getpass doesn't print a newline if the user aborts input with ^C. # Allegedly this behavior is inherited from getpass(3). # A doc bug has been filed at https://bugs.python.org/issue24711 if hide_input: echo(None, err=err) - raise Abort() + raise Abort() from None if value_proc is None: value_proc = convert_type(type, default) - prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type) + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) - while 1: - while 1: + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: value = prompt_func(prompt) if value: break elif default is not None: - if isinstance(value_proc, Path): - # validate Path default value(exists, dir_okay etc.) - value = default - break - return default + value = default + break try: result = value_proc(value) except UsageError as e: - echo('Error: %s' % e.message, err=err) + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 continue if not confirmation_prompt: return result - while 1: - value2 = prompt_func('Repeat for confirmation: ') - if value2: + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: break if value == value2: return result - echo('Error: the two entered values do not match', err=err) + echo(_("Error: The two entered values do not match."), err=err) -def confirm(text, default=False, abort=False, prompt_suffix=': ', - show_default=True, err=False): +def confirm( + text: str, + default: t.Optional[bool] = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: """Prompts for confirmation (yes/no question). If the user aborts the input by sending a interrupt signal this function will catch it and raise a :exc:`Abort` exception. - .. versionadded:: 4.0 - Added the `err` parameter. - :param text: the question to ask. - :param default: the default for the prompt. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. :param abort: if this is set to `True` a negative answer aborts the exception by raising :exc:`Abort`. :param prompt_suffix: a suffix that should be added to the prompt. :param show_default: shows or hides the default value in the prompt. :param err: if set to true the file defaults to ``stderr`` instead of ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. """ - prompt = _build_prompt(text, prompt_suffix, show_default, - default and 'Y/n' or 'y/N') - while 1: + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(prompt, nl=False, err=err) - value = visible_prompt_func('').lower().strip() + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() except (KeyboardInterrupt, EOFError): - raise Abort() - if value in ('y', 'yes'): + raise Abort() from None + if value in ("y", "yes"): rv = True - elif value in ('n', 'no'): + elif value in ("n", "no"): rv = False - elif value == '': + elif default is not None and value == "": rv = default else: - echo('Error: invalid input', err=err) + echo(_("Error: invalid input"), err=err) continue break if abort and not rv: @@ -182,54 +250,10 @@ def confirm(text, default=False, abort=False, prompt_suffix=': ', return rv -def get_terminal_size(): - """Returns the current size of the terminal as tuple in the form - ``(width, height)`` in columns and rows. - """ - # If shutil has get_terminal_size() (Python 3.3 and later) use that - if sys.version_info >= (3, 3): - import shutil - shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None) - if shutil_get_terminal_size: - sz = shutil_get_terminal_size() - return sz.columns, sz.lines - - # We provide a sensible default for get_winterm_size() when being invoked - # inside a subprocess. Without this, it would not provide a useful input. - if get_winterm_size is not None: - size = get_winterm_size() - if size == (0, 0): - return (79, 24) - else: - return size - - def ioctl_gwinsz(fd): - try: - import fcntl - import termios - cr = struct.unpack( - 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) - except Exception: - return - return cr - - cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) - if not cr: - try: - fd = os.open(os.ctermid(), os.O_RDONLY) - try: - cr = ioctl_gwinsz(fd) - finally: - os.close(fd) - except Exception: - pass - if not cr or not cr[0] or not cr[1]: - cr = (os.environ.get('LINES', 25), - os.environ.get('COLUMNS', DEFAULT_COLUMNS)) - return int(cr[1]), int(cr[0]) - - -def echo_via_pager(text_or_generator, color=None): +def echo_via_pager( + text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], + color: t.Optional[bool] = None, +) -> None: """This function takes a text and shows it via an environment specific pager on stdout. @@ -244,25 +268,37 @@ def echo_via_pager(text_or_generator, color=None): color = resolve_color_default(color) if inspect.isgeneratorfunction(text_or_generator): - i = text_or_generator() - elif isinstance(text_or_generator, string_types): + i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)() + elif isinstance(text_or_generator, str): i = [text_or_generator] else: - i = iter(text_or_generator) + i = iter(t.cast(t.Iterable[str], text_or_generator)) # convert every element of i to a text type if necessary - text_generator = (el if isinstance(el, string_types) else text_type(el) - for el in i) + text_generator = (el if isinstance(el, str) else str(el) for el in i) from ._termui_impl import pager + return pager(itertools.chain(text_generator, "\n"), color) -def progressbar(iterable=None, length=None, label=None, show_eta=True, - show_percent=None, show_pos=False, - item_show_func=None, fill_char='#', empty_char='-', - bar_template='%(label)s [%(bar)s] %(info)s', - info_sep=' ', width=36, file=None, color=None): +def progressbar( + iterable: t.Optional[t.Iterable[V]] = None, + length: t.Optional[int] = None, + label: t.Optional[str] = None, + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, +) -> "ProgressBar[V]": """This function creates an iterable context manager that can be used to iterate over something while showing a progress bar. It will either iterate over the `iterable` or `length` items (that are counted @@ -272,11 +308,17 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, will not be rendered if the file is not a terminal. The context manager creates the progress bar. When the context - manager is entered the progress bar is already displayed. With every + manager is entered the progress bar is already created. With every iteration over the progress bar, the iterable passed to the bar is advanced and the bar is updated. When the context manager exits, a newline is printed and the progress bar is finalized on screen. + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + No printing must happen or the progress bar will be unintentionally destroyed. @@ -296,11 +338,19 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, process_chunk(chunk) bar.update(chunks.bytes) - .. versionadded:: 2.0 + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: - .. versionadded:: 4.0 - Added the `color` parameter. Added a `update` method to the - progressbar object. + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) :param iterable: an iterable to iterate over. If not provided the length is required. @@ -319,10 +369,10 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, `False` if not. :param show_pos: enables or disables the absolute position display. The default is `False`. - :param item_show_func: a function called with the current item which - can return a string to show the current item - next to the progress bar. Note that the current - item can be `None`! + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. :param fill_char: the character to use to show the filled part of the progress bar. :param empty_char: the character to use to show the non-filled part of @@ -334,24 +384,57 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, :param info_sep: the separator between multiple info items (eta etc.) :param width: the width of the progress bar in characters, 0 means full terminal width - :param file: the file to write to. If this is not a terminal then - only the label is printed. + :param file: The file to write to. If this is not a terminal then + only the label is printed. :param color: controls if the terminal supports ANSI colors or not. The default is autodetection. This is only needed if ANSI codes are included anywhere in the progress bar output which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + Added the ``update_min_steps`` parameter. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. Added the ``update`` method to + the object. + + .. versionadded:: 2.0 """ from ._termui_impl import ProgressBar + color = resolve_color_default(color) - return ProgressBar(iterable=iterable, length=length, show_eta=show_eta, - show_percent=show_percent, show_pos=show_pos, - item_show_func=item_show_func, fill_char=fill_char, - empty_char=empty_char, bar_template=bar_template, - info_sep=info_sep, file=file, label=label, - width=width, color=color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) -def clear(): +def clear() -> None: """Clears the terminal screen. This will have the effect of clearing the whole visible space of the terminal and moving the cursor to the top left. This does not do anything if not connected to a terminal. @@ -360,17 +443,39 @@ def clear(): """ if not isatty(sys.stdout): return - # If we're on Windows and we don't have colorama available, then we - # clear the screen by shelling out. Otherwise we can use an escape - # sequence. if WIN: - os.system('cls') + os.system("cls") else: - sys.stdout.write('\033[2J\033[1;1H') + sys.stdout.write("\033[2J\033[1;1H") -def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, - blink=None, reverse=None, reset=True): +def _interpret_color( + color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0 +) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bold: t.Optional[bool] = None, + dim: t.Optional[bool] = None, + underline: t.Optional[bool] = None, + overline: t.Optional[bool] = None, + italic: t.Optional[bool] = None, + blink: t.Optional[bool] = None, + reverse: t.Optional[bool] = None, + strikethrough: t.Optional[bool] = None, + reset: bool = True, +) -> str: """Styles a text with ANSI styles and returns the new string. By default the styling is self contained which means that at the end of the string a reset code is issued. This can be prevented by @@ -381,6 +486,7 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, click.echo(click.style('Hello World!', fg='green')) click.echo(click.style('ATTENTION!', blink=True)) click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) Supported color names: @@ -402,10 +508,15 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, * ``bright_white`` * ``reset`` (reset the color code only) - .. versionadded:: 2.0 + If the terminal supports it, color may also be specified as: - .. versionadded:: 7.0 - Added support for bright colors. + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. :param text: the string to style with ansi codes. :param fg: if provided this will become the foreground color. @@ -414,42 +525,73 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, :param dim: if provided this will enable or disable dim mode. This is badly supported. :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. :param blink: if provided this will enable or disable blinking. :param reverse: if provided this will enable or disable inverse rendering (foreground becomes background and the other way round). + :param strikethrough: if provided this will enable or disable + striking through text. :param reset: by default a reset-all code is added at the end of the string which means that styles do not carry over. This can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 """ + if not isinstance(text, str): + text = str(text) + bits = [] + if fg: try: - bits.append('\033[%dm' % (_ansi_colors[fg])) + bits.append(f"\033[{_interpret_color(fg)}m") except KeyError: - raise TypeError('Unknown color %r' % fg) + raise TypeError(f"Unknown color {fg!r}") from None + if bg: try: - bits.append('\033[%dm' % (_ansi_colors[bg] + 10)) + bits.append(f"\033[{_interpret_color(bg, 10)}m") except KeyError: - raise TypeError('Unknown color %r' % bg) + raise TypeError(f"Unknown color {bg!r}") from None + if bold is not None: - bits.append('\033[%dm' % (1 if bold else 22)) + bits.append(f"\033[{1 if bold else 22}m") if dim is not None: - bits.append('\033[%dm' % (2 if dim else 22)) + bits.append(f"\033[{2 if dim else 22}m") if underline is not None: - bits.append('\033[%dm' % (4 if underline else 24)) + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") if blink is not None: - bits.append('\033[%dm' % (5 if blink else 25)) + bits.append(f"\033[{5 if blink else 25}m") if reverse is not None: - bits.append('\033[%dm' % (7 if reverse else 27)) + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") bits.append(text) if reset: bits.append(_ansi_reset_all) - return ''.join(bits) + return "".join(bits) -def unstyle(text): +def unstyle(text: str) -> str: """Removes ANSI styling information from a string. Usually it's not necessary to use this function as Click's echo function will automatically remove styling if necessary. @@ -461,7 +603,14 @@ def unstyle(text): return strip_ansi(text) -def secho(message=None, file=None, nl=True, err=False, color=None, **styles): +def secho( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.AnyStr]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, + **styles: t.Any, +) -> None: """This function combines :func:`echo` and :func:`style` into one call. As such the following two calls are the same:: @@ -471,15 +620,31 @@ def secho(message=None, file=None, nl=True, err=False, color=None, **styles): All keyword arguments are forwarded to the underlying functions depending on which one they go with. + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + .. versionadded:: 2.0 """ - if message is not None: + if message is not None and not isinstance(message, (bytes, bytearray)): message = style(message, **styles) + return echo(message, file=file, nl=nl, err=err, color=color) -def edit(text=None, editor=None, env=None, require_save=True, - extension='.txt', filename=None): +def edit( + text: t.Optional[t.AnyStr] = None, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + filename: t.Optional[str] = None, +) -> t.Optional[t.AnyStr]: r"""Edits the given text in the defined editor. If an editor is given (should be the full path to the executable but the regular operating system search path is used for finding the executable) it overrides @@ -508,14 +673,17 @@ def edit(text=None, editor=None, env=None, require_save=True, file as an indirection in that case. """ from ._termui_impl import Editor - editor = Editor(editor=editor, env=env, require_save=require_save, - extension=extension) + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + if filename is None: - return editor.edit(text) - editor.edit_file(filename) + return ed.edit(text) + + ed.edit_file(filename) + return None -def launch(url, wait=False, locate=False): +def launch(url: str, wait: bool = False, locate: bool = False) -> int: """This function launches the given URL (or filename) in the default viewer application for this file type. If this is an executable, it might launch the executable in a new session. The return value is @@ -530,7 +698,9 @@ def launch(url, wait=False, locate=False): .. versionadded:: 2.0 :param url: URL or filename of the thing to launch. - :param wait: waits for the program to stop. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. :param locate: if this is set to `True` then instead of launching the application associated with the URL it will attempt to launch a file manager with the file located. This @@ -538,15 +708,16 @@ def launch(url, wait=False, locate=False): the filesystem. """ from ._termui_impl import open_url + return open_url(url, wait=wait, locate=locate) # If this is provided, getchar() calls into this instead. This is used # for unittesting purposes. -_getchar = None +_getchar: t.Optional[t.Callable[[bool], str]] = None -def getchar(echo=False): +def getchar(echo: bool = False) -> str: """Fetches a single character from the terminal and returns it. This will always return a unicode character and under certain rare circumstances this might return more than one character. The @@ -566,18 +737,23 @@ def getchar(echo=False): :param echo: if set to `True`, the character read will also show up on the terminal. The default is to not show it. """ - f = _getchar - if f is None: + global _getchar + + if _getchar is None: from ._termui_impl import getchar as f - return f(echo) + + _getchar = f + + return _getchar(echo) -def raw_terminal(): +def raw_terminal() -> t.ContextManager[int]: from ._termui_impl import raw_terminal as f + return f() -def pause(info='Press any key to continue ...', err=False): +def pause(info: t.Optional[str] = None, err: bool = False) -> None: """This command stops execution and waits for the user to press any key to continue. This is similar to the Windows batch "pause" command. If the program is not run through a terminal, this command @@ -588,12 +764,17 @@ def pause(info='Press any key to continue ...', err=False): .. versionadded:: 4.0 Added the `err` parameter. - :param info: the info string to print before pausing. + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. :param err: if set to message goes to ``stderr`` instead of ``stdout``, the same as with echo. """ if not isatty(sys.stdin) or not isatty(sys.stdout): return + + if info is None: + info = _("Press any key to continue...") + try: if info: echo(info, nl=False, err=err) diff --git a/libs/common/click/testing.py b/libs/common/click/testing.py index 1b2924e0..e395c2ed 100644 --- a/libs/common/click/testing.py +++ b/libs/common/click/testing.py @@ -1,86 +1,128 @@ -import os -import sys -import shutil -import tempfile import contextlib +import io +import os import shlex +import shutil +import sys +import tempfile +import typing as t +from types import TracebackType -from ._compat import iteritems, PY2, string_types +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from .core import BaseCommand -# If someone wants to vendor click, we want to ensure the -# correct package is discovered. Ideally we could use a -# relative import here but unfortunately Python does not -# support that. -clickpkg = sys.modules[__name__.rsplit('.', 1)[0]] - - -if PY2: - from cStringIO import StringIO -else: - import io - from ._compat import _find_binary_reader - - -class EchoingStdin(object): - - def __init__(self, input, output): +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: self._input = input self._output = output + self._paused = False - def __getattr__(self, x): + def __getattr__(self, x: str) -> t.Any: return getattr(self._input, x) - def _echo(self, rv): - self._output.write(rv) + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + return rv - def read(self, n=-1): + def read(self, n: int = -1) -> bytes: return self._echo(self._input.read(n)) - def readline(self, n=-1): + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: return self._echo(self._input.readline(n)) - def readlines(self): + def readlines(self) -> t.List[bytes]: return [self._echo(x) for x in self._input.readlines()] - def __iter__(self): + def __iter__(self) -> t.Iterator[bytes]: return iter(self._echo(x) for x in self._input) - def __repr__(self): + def __repr__(self) -> str: return repr(self._input) -def make_input_stream(input, charset): +@contextlib.contextmanager +def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: t.Optional[t.Union[str, bytes, t.IO]], charset: str +) -> t.BinaryIO: # Is already an input stream. - if hasattr(input, 'read'): - if PY2: - return input - rv = _find_binary_reader(input) + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast(t.IO, input)) + if rv is not None: return rv - raise TypeError('Could not find binary reader for input stream.') + + raise TypeError("Could not find binary reader for input stream.") if input is None: - input = b'' - elif not isinstance(input, bytes): + input = b"" + elif isinstance(input, str): input = input.encode(charset) - if PY2: - return StringIO(input) - return io.BytesIO(input) + + return io.BytesIO(t.cast(bytes, input)) -class Result(object): +class Result: """Holds the captured result of an invoked CLI script.""" - def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code, - exception, exc_info=None): + def __init__( + self, + runner: "CliRunner", + stdout_bytes: bytes, + stderr_bytes: t.Optional[bytes], + return_value: t.Any, + exit_code: int, + exception: t.Optional[BaseException], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = None, + ): #: The runner that created the result self.runner = runner #: The standard output as bytes. self.stdout_bytes = stdout_bytes - #: The standard error as bytes, or False(y) if not available + #: The standard error as bytes, or None if not available self.stderr_bytes = stderr_bytes + #: The value returned from the invoked command. + #: + #: .. versionadded:: 8.0 + self.return_value = return_value #: The exit code as integer. self.exit_code = exit_code #: The exception that happened if one did. @@ -89,41 +131,38 @@ class Result(object): self.exc_info = exc_info @property - def output(self): + def output(self) -> str: """The (standard) output as unicode string.""" return self.stdout @property - def stdout(self): + def stdout(self) -> str: """The standard output as unicode string.""" - return self.stdout_bytes.decode(self.runner.charset, 'replace') \ - .replace('\r\n', '\n') - - @property - def stderr(self): - """The standard error as unicode string.""" - if not self.stderr_bytes: - raise ValueError("stderr not separately captured") - return self.stderr_bytes.decode(self.runner.charset, 'replace') \ - .replace('\r\n', '\n') - - - def __repr__(self): - return '<%s %s>' % ( - type(self).__name__, - self.exception and repr(self.exception) or 'okay', + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" ) + @property + def stderr(self) -> str: + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) -class CliRunner(object): + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: """The CLI runner provides functionality to invoke a Click command line script for unittesting purposes in a isolated environment. This only works in single-threaded systems without any concurrency as it changes the global interpreter state. - :param charset: the character set for the input and output data. This is - UTF-8 by default and should not be changed currently as - the reporting to Click only works in Python 2 properly. + :param charset: the character set for the input and output data. :param env: a dictionary with environment variables for overriding. :param echo_stdin: if this is set to `True`, then reading from stdin writes to stdout. This is useful for showing examples in @@ -136,23 +175,28 @@ class CliRunner(object): independently """ - def __init__(self, charset=None, env=None, echo_stdin=False, - mix_stderr=True): - if charset is None: - charset = 'utf-8' + def __init__( + self, + charset: str = "utf-8", + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + echo_stdin: bool = False, + mix_stderr: bool = True, + ) -> None: self.charset = charset self.env = env or {} self.echo_stdin = echo_stdin self.mix_stderr = mix_stderr - def get_default_prog_name(self, cli): + def get_default_prog_name(self, cli: "BaseCommand") -> str: """Given a command object it will return the default program name for it. The default is the `name` attribute or ``"root"`` if not set. """ - return cli.name or 'root' + return cli.name or "root" - def make_env(self, overrides=None): + def make_env( + self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None + ) -> t.Mapping[str, t.Optional[str]]: """Returns the environment overrides for invoking a script.""" rv = dict(self.env) if overrides: @@ -160,7 +204,12 @@ class CliRunner(object): return rv @contextlib.contextmanager - def isolation(self, input=None, env=None, color=False): + def isolation( + self, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + color: bool = False, + ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: """A context manager that sets up the isolation for invoking of a command line tool. This sets up stdin with the given input data and `os.environ` with the overrides from the given dictionary. @@ -169,87 +218,107 @@ class CliRunner(object): This is automatically done in the :meth:`invoke` method. - .. versionadded:: 4.0 - The ``color`` parameter was added. - :param input: the input stream to put into sys.stdin. :param env: the environment overrides as dictionary. :param color: whether the output should contain color codes. The application can still override this explicitly. + + .. versionchanged:: 8.0 + ``stderr`` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. """ - input = make_input_stream(input, self.charset) + bytes_input = make_input_stream(input, self.charset) + echo_input = None old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr - old_forced_width = clickpkg.formatting.FORCED_WIDTH - clickpkg.formatting.FORCED_WIDTH = 80 + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 env = self.make_env(env) - if PY2: - bytes_output = StringIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - sys.stdout = bytes_output - if not self.mix_stderr: - bytes_error = StringIO() - sys.stderr = bytes_error - else: - bytes_output = io.BytesIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - input = io.TextIOWrapper(input, encoding=self.charset) - sys.stdout = io.TextIOWrapper( - bytes_output, encoding=self.charset) - if not self.mix_stderr: - bytes_error = io.BytesIO() - sys.stderr = io.TextIOWrapper( - bytes_error, encoding=self.charset) + bytes_output = io.BytesIO() + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, bytes_output) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + bytes_output, encoding=self.charset, name="", mode="w" + ) + + bytes_error = None if self.mix_stderr: sys.stderr = sys.stdout + else: + bytes_error = io.BytesIO() + sys.stderr = _NamedTextIOWrapper( + bytes_error, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) - sys.stdin = input - - def visible_input(prompt=None): - sys.stdout.write(prompt or '') - val = input.readline().rstrip('\r\n') - sys.stdout.write(val + '\n') + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(prompt or "") + val = text_input.readline().rstrip("\r\n") + sys.stdout.write(f"{val}\n") sys.stdout.flush() return val - def hidden_input(prompt=None): - sys.stdout.write((prompt or '') + '\n') + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") sys.stdout.flush() - return input.readline().rstrip('\r\n') + return text_input.readline().rstrip("\r\n") - def _getchar(echo): + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: char = sys.stdin.read(1) + if echo: sys.stdout.write(char) - sys.stdout.flush() + + sys.stdout.flush() return char default_color = color - def should_strip_ansi(stream=None, color=None): + def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None + ) -> bool: if color is None: return not default_color return not color - old_visible_prompt_func = clickpkg.termui.visible_prompt_func - old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func - old__getchar_func = clickpkg.termui._getchar - old_should_strip_ansi = clickpkg.utils.should_strip_ansi - clickpkg.termui.visible_prompt_func = visible_input - clickpkg.termui.hidden_prompt_func = hidden_input - clickpkg.termui._getchar = _getchar - clickpkg.utils.should_strip_ansi = should_strip_ansi + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore old_env = {} try: - for key, value in iteritems(env): + for key, value in env.items(): old_env[key] = os.environ.get(key) if value is None: try: @@ -258,9 +327,9 @@ class CliRunner(object): pass else: os.environ[key] = value - yield (bytes_output, not self.mix_stderr and bytes_error) + yield (bytes_output, bytes_error) finally: - for key, value in iteritems(old_env): + for key, value in old_env.items(): if value is None: try: del os.environ[key] @@ -271,14 +340,22 @@ class CliRunner(object): sys.stdout = old_stdout sys.stderr = old_stderr sys.stdin = old_stdin - clickpkg.termui.visible_prompt_func = old_visible_prompt_func - clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func - clickpkg.termui._getchar = old__getchar_func - clickpkg.utils.should_strip_ansi = old_should_strip_ansi - clickpkg.formatting.FORCED_WIDTH = old_forced_width + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + formatting.FORCED_WIDTH = old_forced_width - def invoke(self, cli, args=None, input=None, env=None, - catch_exceptions=True, color=False, mix_stderr=False, **extra): + def invoke( + self, + cli: "BaseCommand", + args: t.Optional[t.Union[str, t.Sequence[str]]] = None, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + catch_exceptions: bool = True, + color: bool = False, + **extra: t.Any, + ) -> Result: """Invokes a command in an isolated environment. The arguments are forwarded directly to the command line script, the `extra` keyword arguments are passed to the :meth:`~clickpkg.Command.main` function of @@ -286,16 +363,6 @@ class CliRunner(object): This returns a :class:`Result` object. - .. versionadded:: 3.0 - The ``catch_exceptions`` parameter was added. - - .. versionchanged:: 3.0 - The result object now has an `exc_info` attribute with the - traceback if available. - - .. versionadded:: 4.0 - The ``color`` parameter was added. - :param cli: the command to invoke :param args: the arguments to invoke. It may be given as an iterable or a string. When given as string it will be interpreted @@ -308,13 +375,28 @@ class CliRunner(object): :param extra: the keyword arguments to pass to :meth:`main`. :param color: whether the output should contain color codes. The application can still override this explicitly. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. """ exc_info = None with self.isolation(input=input, env=env, color=color) as outstreams: - exception = None + return_value = None + exception: t.Optional[BaseException] = None exit_code = 0 - if isinstance(args, string_types): + if isinstance(args, str): args = shlex.split(args) try: @@ -323,20 +405,23 @@ class CliRunner(object): prog_name = self.get_default_prog_name(cli) try: - cli.main(args=args or (), prog_name=prog_name, **extra) + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) except SystemExit as e: exc_info = sys.exc_info() - exit_code = e.code - if exit_code is None: - exit_code = 0 + e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code) - if exit_code != 0: + if e_code is None: + e_code = 0 + + if e_code != 0: exception = e - if not isinstance(exit_code, int): - sys.stdout.write(str(exit_code)) - sys.stdout.write('\n') - exit_code = 1 + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code except Exception as e: if not catch_exceptions: @@ -347,28 +432,48 @@ class CliRunner(object): finally: sys.stdout.flush() stdout = outstreams[0].getvalue() - stderr = outstreams[1] and outstreams[1].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() # type: ignore - return Result(runner=self, - stdout_bytes=stdout, - stderr_bytes=stderr, - exit_code=exit_code, - exception=exception, - exc_info=exc_info) + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) @contextlib.contextmanager - def isolated_filesystem(self): - """A context manager that creates a temporary folder and changes - the current working directory to it for isolated filesystem tests. + def isolated_filesystem( + self, temp_dir: t.Optional[t.Union[str, os.PathLike]] = None + ) -> t.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. """ cwd = os.getcwd() - t = tempfile.mkdtemp() - os.chdir(t) + dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var] + os.chdir(dt) + try: - yield t + yield t.cast(str, dt) finally: os.chdir(cwd) - try: - shutil.rmtree(t) - except (OSError, IOError): - pass + + if temp_dir is None: + try: + shutil.rmtree(dt) + except OSError: # noqa: B014 + pass diff --git a/libs/common/click/types.py b/libs/common/click/types.py index 1f88032f..b45ee53d 100644 --- a/libs/common/click/types.py +++ b/libs/common/click/types.py @@ -1,30 +1,47 @@ import os import stat +import typing as t from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext -from ._compat import open_stream, text_type, filename_to_ui, \ - get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 +from ._compat import _get_argv_encoding +from ._compat import get_filesystem_encoding +from ._compat import open_stream from .exceptions import BadParameter -from .utils import safecall, LazyFile +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem -class ParamType(object): - """Helper for converting values through types. The following is - necessary for a valid type: +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. - * it needs a name - * it needs to pass through None unchanged - * it needs to convert from a string - * it needs to convert its result type through unchanged - (eg: needs to be idempotent) - * it needs to be able to deal with param and context being `None`. - This can be the case when the object is used with prompt - inputs. + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. """ - is_composite = False + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 #: the descriptive name of this type - name = None + name: str #: if a list of this type is expected and the value is pulled from a #: string environment variable, this is what splits it up. `None` @@ -32,29 +49,73 @@ class ParamType(object): #: whitespace splits them up. The exception are paths and files which #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on #: Windows). - envvar_list_splitter = None + envvar_list_splitter: t.ClassVar[t.Optional[str]] = None - def __call__(self, value, param=None, ctx=None): + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> t.Any: if value is not None: return self.convert(value, param, ctx) - def get_metavar(self, param): + def get_metavar(self, param: "Parameter") -> t.Optional[str]: """Returns the metavar default for this param if it provides one.""" - def get_missing_message(self, param): + def get_missing_message(self, param: "Parameter") -> t.Optional[str]: """Optionally might return extra information about a missing parameter. .. versionadded:: 2.0 """ - def convert(self, value, param, ctx): - """Converts the value. This is not invoked for values that are - `None` (the missing value). + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. """ return value - def split_envvar_value(self, rv): + def split_envvar_value(self, rv: str) -> t.Sequence[str]: """Given a value from an environment variable this splits it up into small chunks depending on the defined envvar list splitter. @@ -62,52 +123,85 @@ class ParamType(object): then leading and trailing whitespace is ignored. Otherwise, leading and trailing splitters usually lead to empty items being included. """ - return (rv or '').split(self.envvar_list_splitter) + return (rv or "").split(self.envvar_list_splitter) - def fail(self, message, param=None, ctx=None): + def fail( + self, + message: str, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> "t.NoReturn": """Helper method to fail with an invalid value message.""" raise BadParameter(message, ctx=ctx, param=param) + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + class CompositeParamType(ParamType): is_composite = True @property - def arity(self): + def arity(self) -> int: # type: ignore raise NotImplementedError() class FuncParamType(ParamType): - - def __init__(self, func): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: self.name = func.__name__ self.func = func - def convert(self, value, param, ctx): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: return self.func(value) except ValueError: try: - value = text_type(value) + value = str(value) except UnicodeError: - value = str(value).decode('utf-8', 'replace') + value = value.decode("utf-8", "replace") + self.fail(value, param, ctx) class UnprocessedParamType(ParamType): - name = 'text' + name = "text" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: return value - def __repr__(self): - return 'UNPROCESSED' + def __repr__(self) -> str: + return "UNPROCESSED" class StringParamType(ParamType): - name = 'text' + name = "text" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: if isinstance(value, bytes): enc = _get_argv_encoding() try: @@ -118,12 +212,14 @@ class StringParamType(ParamType): try: value = value.decode(fs_enc) except UnicodeError: - value = value.decode('utf-8', 'replace') + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") return value - return value + return str(value) - def __repr__(self): - return 'STRING' + def __repr__(self) -> str: + return "STRING" class Choice(ParamType): @@ -133,54 +229,104 @@ class Choice(ParamType): You should only pass a list or tuple of choices. Other iterables (like generators) may lead to surprising results. + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + See :ref:`choice-opts` for an example. :param case_sensitive: Set to false to make choices case insensitive. Defaults to true. """ - name = 'choice' + name = "choice" - def __init__(self, choices, case_sensitive=True): + def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None: self.choices = choices self.case_sensitive = case_sensitive - def get_metavar(self, param): - return '[%s]' % '|'.join(self.choices) + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict - def get_missing_message(self, param): - return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices) + def get_metavar(self, param: "Parameter") -> str: + choices_str = "|".join(self.choices) - def convert(self, value, param, ctx): - # Exact match - if value in self.choices: - return value + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: "Parameter") -> str: + return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices)) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: # Match through normalization and case sensitivity # first do token_normalize_func, then lowercase # preserve original `value` to produce an accurate message in # `self.fail` normed_value = value - normed_choices = self.choices + normed_choices = {choice: choice for choice in self.choices} - if ctx is not None and \ - ctx.token_normalize_func is not None: + if ctx is not None and ctx.token_normalize_func is not None: normed_value = ctx.token_normalize_func(value) - normed_choices = [ctx.token_normalize_func(choice) for choice in - self.choices] + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } if not self.case_sensitive: - normed_value = normed_value.lower() - normed_choices = [choice.lower() for choice in normed_choices] + normed_value = normed_value.casefold() + normed_choices = { + normed_choice.casefold(): original + for normed_choice, original in normed_choices.items() + } if normed_value in normed_choices: - return normed_value + return normed_choices[normed_value] - self.fail('invalid choice: %s. (choose from %s)' % - (value, ', '.join(self.choices)), param, ctx) + choices_str = ", ".join(map(repr, self.choices)) + self.fail( + ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str), + param, + ctx, + ) - def __repr__(self): - return 'Choice(%r)' % list(self.choices) + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] class DateTime(ParamType): @@ -203,175 +349,289 @@ class DateTime(ParamType): ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, ``'%Y-%m-%d %H:%M:%S'``. """ - name = 'datetime' - def __init__(self, formats=None): - self.formats = formats or [ - '%Y-%m-%d', - '%Y-%m-%dT%H:%M:%S', - '%Y-%m-%d %H:%M:%S' - ] + name = "datetime" - def get_metavar(self, param): - return '[{}]'.format('|'.join(self.formats)) + def __init__(self, formats: t.Optional[t.Sequence[str]] = None): + self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"] - def _try_to_convert_date(self, value, format): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: try: return datetime.strptime(value, format) except ValueError: return None - def convert(self, value, param, ctx): - # Exact match + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, datetime): + return value + for format in self.formats: - dtime = self._try_to_convert_date(value, format) - if dtime: - return dtime + converted = self._try_to_convert_date(value, format) + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) self.fail( - 'invalid datetime format: {}. (choose from {})'.format( - value, ', '.join(self.formats))) + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) - def __repr__(self): - return 'DateTime' + def __repr__(self) -> str: + return "DateTime" -class IntParamType(ParamType): - name = 'integer' +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[t.Type] - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: - return int(value) - except (ValueError, UnicodeError): - self.fail('%s is not a valid integer' % value, param, ctx) - - def __repr__(self): - return 'INT' + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) -class IntRange(IntParamType): - """A parameter that works similar to :data:`click.INT` but restricts - the value to fit into a range. The default behavior is to fail if the - value falls outside the range, but it can also be silently clamped - between the two edges. - - See :ref:`ranges` for an example. - """ - name = 'integer range' - - def __init__(self, min=None, max=None, clamp=False): +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: self.min = min self.max = max + self.min_open = min_open + self.max_open = max_open self.clamp = clamp - def convert(self, value, param, ctx): - rv = IntParamType.convert(self, value, param, ctx) + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + if self.clamp: - if self.min is not None and rv < self.min: - return self.min - if self.max is not None and rv > self.max: - return self.max - if self.min is not None and rv < self.min or \ - self.max is not None and rv > self.max: - if self.min is None: - self.fail('%s is bigger than the maximum valid value ' - '%s.' % (rv, self.max), param, ctx) - elif self.max is None: - self.fail('%s is smaller than the minimum valid value ' - '%s.' % (rv, self.min), param, ctx) - else: - self.fail('%s is not in the valid range of %s to %s.' - % (rv, self.min, self.max), param, ctx) + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + return rv - def __repr__(self): - return 'IntRange(%r, %r)' % (self.min, self.max) + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" -class FloatParamType(ParamType): - name = 'float' +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int - def convert(self, value, param, ctx): - try: - return float(value) - except (UnicodeError, ValueError): - self.fail('%s is not a valid floating point value' % - value, param, ctx) - - def __repr__(self): - return 'FLOAT' + def __repr__(self) -> str: + return "INT" -class FloatRange(FloatParamType): - """A parameter that works similar to :data:`click.FLOAT` but restricts - the value to fit into a range. The default behavior is to fail if the - value falls outside the range, but it can also be silently clamped - between the two edges. +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. - See :ref:`ranges` for an example. + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. """ - name = 'float range' - def __init__(self, min=None, max=None, clamp=False): - self.min = min - self.max = max - self.clamp = clamp + name = "integer range" - def convert(self, value, param, ctx): - rv = FloatParamType.convert(self, value, param, ctx) - if self.clamp: - if self.min is not None and rv < self.min: - return self.min - if self.max is not None and rv > self.max: - return self.max - if self.min is not None and rv < self.min or \ - self.max is not None and rv > self.max: - if self.min is None: - self.fail('%s is bigger than the maximum valid value ' - '%s.' % (rv, self.max), param, ctx) - elif self.max is None: - self.fail('%s is smaller than the minimum valid value ' - '%s.' % (rv, self.min), param, ctx) - else: - self.fail('%s is not in the valid range of %s to %s.' - % (rv, self.min, self.max), param, ctx) - return rv + def _clamp( # type: ignore + self, bound: int, dir: "te.Literal[1, -1]", open: bool + ) -> int: + if not open: + return bound - def __repr__(self): - return 'FloatRange(%r, %r)' % (self.min, self.max) + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + if not open: + return bound + + # Could use Python 3.9's math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") class BoolParamType(ParamType): - name = 'boolean' + name = "boolean" - def convert(self, value, param, ctx): - if isinstance(value, bool): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if value in {False, True}: return bool(value) - value = value.lower() - if value in ('true', 't', '1', 'yes', 'y'): - return True - elif value in ('false', 'f', '0', 'no', 'n'): - return False - self.fail('%s is not a valid boolean' % value, param, ctx) - def __repr__(self): - return 'BOOL' + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" class UUIDParameterType(ParamType): - name = 'uuid' + name = "uuid" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: import uuid - try: - if PY2 and isinstance(value, text_type): - value = value.encode('ascii') - return uuid.UUID(value) - except (UnicodeError, ValueError): - self.fail('%s is not a valid UUID value' % value, param, ctx) - def __repr__(self): - return 'UUID' + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" class File(ParamType): @@ -400,43 +660,64 @@ class File(ParamType): See :ref:`file-args` for more information. """ - name = 'filename' + + name = "filename" envvar_list_splitter = os.path.pathsep - def __init__(self, mode='r', encoding=None, errors='strict', lazy=None, - atomic=False): + def __init__( + self, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: t.Optional[bool] = None, + atomic: bool = False, + ) -> None: self.mode = mode self.encoding = encoding self.errors = errors self.lazy = lazy self.atomic = atomic - def resolve_lazy_flag(self, value): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: t.Any) -> bool: if self.lazy is not None: return self.lazy - if value == '-': + if value == "-": return False - elif 'w' in self.mode: + elif "w" in self.mode: return True return False - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: - if hasattr(value, 'read') or hasattr(value, 'write'): + if hasattr(value, "read") or hasattr(value, "write"): return value lazy = self.resolve_lazy_flag(value) if lazy: - f = LazyFile(value, self.mode, self.encoding, self.errors, - atomic=self.atomic) + f: t.IO = t.cast( + t.IO, + LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ), + ) + if ctx is not None: - ctx.call_on_close(f.close_intelligently) + ctx.call_on_close(f.close_intelligently) # type: ignore + return f - f, should_close = open_stream(value, self.mode, - self.encoding, self.errors, - atomic=self.atomic) + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + # If a context is provided, we automatically close the file # at the end of the context execution (or flush out). If a # context does not exist, it's the caller's responsibility to @@ -447,118 +728,207 @@ class File(ParamType): ctx.call_on_close(safecall(f.close)) else: ctx.call_on_close(safecall(f.flush)) + return f - except (IOError, OSError) as e: - self.fail('Could not open file: %s: %s' % ( - filename_to_ui(value), - get_streerror(e), - ), param, ctx) + except OSError as e: # noqa: B014 + self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] class Path(ParamType): - """The path type is similar to the :class:`File` type but it performs - different checks. First of all, instead of returning an open file - handle it returns just the filename. Secondly, it can perform various - basic checks about what the file or directory should be. + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``type=pathlib.Path``. .. versionchanged:: 6.0 - `allow_dash` was added. - - :param exists: if set to true, the file or directory needs to exist for - this value to be valid. If this is not required and a - file does indeed not exist, then all further checks are - silently skipped. - :param file_okay: controls if a file is a possible value. - :param dir_okay: controls if a directory is a possible value. - :param writable: if true, a writable check is performed. - :param readable: if true, a readable check is performed. - :param resolve_path: if this is true, then the path is fully resolved - before the value is passed onwards. This means - that it's absolute and symlinks are resolved. It - will not expand a tilde-prefix, as this is - supposed to be done by the shell only. - :param allow_dash: If this is set to `True`, a single dash to indicate - standard streams is permitted. - :param path_type: optionally a string type that should be used to - represent the path. The default is `None` which - means the return value will be either bytes or - unicode depending on what makes most sense given the - input data Click deals with. + Added the ``allow_dash`` parameter. """ + envvar_list_splitter = os.path.pathsep - def __init__(self, exists=False, file_okay=True, dir_okay=True, - writable=False, readable=True, resolve_path=False, - allow_dash=False, path_type=None): + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: t.Optional[t.Type] = None, + executable: bool = False, + ): self.exists = exists self.file_okay = file_okay self.dir_okay = dir_okay - self.writable = writable self.readable = readable + self.writable = writable + self.executable = executable self.resolve_path = resolve_path self.allow_dash = allow_dash self.type = path_type if self.file_okay and not self.dir_okay: - self.name = 'file' - self.path_type = 'File' + self.name = _("file") elif self.dir_okay and not self.file_okay: - self.name = 'directory' - self.path_type = 'Directory' + self.name = _("directory") else: - self.name = 'path' - self.path_type = 'Path' + self.name = _("path") - def coerce_path_result(self, rv): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result(self, rv: t.Any) -> t.Any: if self.type is not None and not isinstance(rv, self.type): - if self.type is text_type: - rv = rv.decode(get_filesystem_encoding()) + if self.type is str: + rv = os.fsdecode(rv) + elif self.type is bytes: + rv = os.fsencode(rv) else: - rv = rv.encode(get_filesystem_encoding()) + rv = self.type(rv) + return rv - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: rv = value - is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") if not is_dash: if self.resolve_path: - rv = os.path.realpath(rv) + # os.path.realpath doesn't resolve symlinks on Windows + # until Python 3.8. Use pathlib for now. + import pathlib + + rv = os.fsdecode(pathlib.Path(rv).resolve()) try: st = os.stat(rv) except OSError: if not self.exists: return self.coerce_path_result(rv) - self.fail('%s "%s" does not exist.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) if not self.file_okay and stat.S_ISREG(st.st_mode): - self.fail('%s "%s" is a file.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) if not self.dir_okay and stat.S_ISDIR(st.st_mode): - self.fail('%s "%s" is a directory.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) - if self.writable and not os.access(value, os.W_OK): - self.fail('%s "%s" is not writable.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) - if self.readable and not os.access(value, os.R_OK): - self.fail('%s "%s" is not readable.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} '{filename}' is a directory.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) return self.coerce_path_result(rv) + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + class Tuple(CompositeParamType): """The default behavior of Click is to apply a type on a value directly. @@ -574,72 +944,107 @@ class Tuple(CompositeParamType): :param types: a list of types that should be used for the tuple items. """ - def __init__(self, types): + def __init__(self, types: t.Sequence[t.Union[t.Type, ParamType]]) -> None: self.types = [convert_type(ty) for ty in types] - @property - def name(self): - return "<" + " ".join(ty.name for ty in self.types) + ">" + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict @property - def arity(self): + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore return len(self.types) - def convert(self, value, param, ctx): - if len(value) != len(self.types): - raise TypeError('It would appear that nargs is set to conflict ' - 'with the composite type arity.') + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) -def convert_type(ty, default=None): - """Converts a callable or python ty into the most appropriate param - ty. +def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. """ guessed_type = False + if ty is None and default is not None: - if isinstance(default, tuple): - ty = tuple(map(type, default)) + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) else: ty = type(default) + guessed_type = True if isinstance(ty, tuple): return Tuple(ty) + if isinstance(ty, ParamType): return ty - if ty is text_type or ty is str or ty is None: + + if ty is str or ty is None: return STRING + if ty is int: return INT - # Booleans are only okay if not guessed. This is done because for - # flags the default value is actually a bit of a lie in that it - # indicates which of the flags is the one we want. See get_default() - # for more information. - if ty is bool and not guessed_type: - return BOOL + if ty is float: return FLOAT + + if ty is bool: + return BOOL + if guessed_type: return STRING - # Catch a common mistake if __debug__: try: if issubclass(ty, ParamType): - raise AssertionError('Attempted to use an uninstantiated ' - 'parameter type (%s).' % ty) + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) except TypeError: + # ty is an instance (correct), so issubclass fails. pass + return FuncParamType(ty) #: A dummy parameter type that just does nothing. From a user's -#: perspective this appears to just be the same as `STRING` but internally -#: no string conversion takes place. This is necessary to achieve the -#: same bytes/unicode behavior on Python 2/3 in situations where you want -#: to not convert argument types. This is usually useful when working -#: with file paths as they can appear in bytes and unicode. +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. #: #: For path related uses the :class:`Path` type is a better choice but #: there are situations where an unprocessed type is useful which is why diff --git a/libs/common/click/utils.py b/libs/common/click/utils.py index fc84369f..8283788a 100644 --- a/libs/common/click/utils.py +++ b/libs/common/click/utils.py @@ -1,92 +1,131 @@ import os +import re import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN from .globals import resolve_color_default -from ._compat import text_type, open_stream, get_filesystem_encoding, \ - get_streerror, string_types, PY2, binary_streams, text_streams, \ - filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \ - _default_text_stdout, _default_text_stderr, is_bytes, WIN +if t.TYPE_CHECKING: + import typing_extensions as te -if not PY2: - from ._compat import _find_binary_writer -elif WIN: - from ._winconsole import _get_windows_argv, \ - _hash_py_argv, _initial_argv_hash +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) -echo_native_types = string_types + (bytes, bytearray) +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() -def _posixify(name): - return '-'.join(name.split()).lower() - - -def safecall(func): +def safecall(func: F) -> F: """Wraps a function so that it swallows exceptions.""" - def wrapper(*args, **kwargs): + + def wrapper(*args, **kwargs): # type: ignore try: return func(*args, **kwargs) except Exception: pass - return wrapper + + return update_wrapper(t.cast(F, wrapper), func) -def make_str(value): +def make_str(value: t.Any) -> str: """Converts a value into a valid string.""" if isinstance(value, bytes): try: return value.decode(get_filesystem_encoding()) except UnicodeError: - return value.decode('utf-8', 'replace') - return text_type(value) + return value.decode("utf-8", "replace") + return str(value) -def make_default_short_help(help, max_length=45): - """Return a condensed version of help string.""" +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + total_length = 0 - result = [] - done = False + last_index = len(words) - 1 - for word in words: - if word[-1:] == '.': - done = True - new_length = result and 1 + len(word) or len(word) - if total_length + new_length > max_length: - result.append('...') - done = True - else: - if result: - result.append(' ') - result.append(word) - if done: + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate break - total_length += new_length - return ''.join(result) + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." -class LazyFile(object): +class LazyFile: """A lazy file works like a regular file but it does not fully open the file but it does perform some basic checks early to see if the filename parameter does make sense. This is useful for safely opening files for writing. """ - def __init__(self, filename, mode='r', encoding=None, errors='strict', - atomic=False): + def __init__( + self, + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, + ): self.name = filename self.mode = mode self.encoding = encoding self.errors = errors self.atomic = atomic + self._f: t.Optional[t.IO] - if filename == '-': - self._f, self.should_close = open_stream(filename, mode, - encoding, errors) + if filename == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) else: - if 'r' in mode: + if "r" in mode: # Open and close the file in case we're opening it for # reading so that we can catch at least some errors in # some cases early. @@ -94,15 +133,15 @@ class LazyFile(object): self._f = None self.should_close = True - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self.open(), name) - def __repr__(self): + def __repr__(self) -> str: if self._f is not None: return repr(self._f) - return '' % (self.name, self.mode) + return f"" - def open(self): + def open(self) -> t.IO: """Opens the file if it's not yet open. This call might fail with a :exc:`FileError`. Not handling this error will produce an error that Click shows. @@ -110,106 +149,103 @@ class LazyFile(object): if self._f is not None: return self._f try: - rv, self.should_close = open_stream(self.name, self.mode, - self.encoding, - self.errors, - atomic=self.atomic) - except (IOError, OSError) as e: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: # noqa: E402 from .exceptions import FileError - raise FileError(self.name, hint=get_streerror(e)) + + raise FileError(self.name, hint=e.strerror) from e self._f = rv return rv - def close(self): + def close(self) -> None: """Closes the underlying file, no matter what.""" if self._f is not None: self._f.close() - def close_intelligently(self): + def close_intelligently(self) -> None: """This function only closes the file if it was opened by the lazy file wrapper. For instance this will never close stdin. """ if self.should_close: self.close() - def __enter__(self): + def __enter__(self) -> "LazyFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.close_intelligently() - def __iter__(self): + def __iter__(self) -> t.Iterator[t.AnyStr]: self.open() - return iter(self._f) + return iter(self._f) # type: ignore -class KeepOpenFile(object): - - def __init__(self, file): +class KeepOpenFile: + def __init__(self, file: t.IO) -> None: self._file = file - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._file, name) - def __enter__(self): + def __enter__(self) -> "KeepOpenFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore pass - def __repr__(self): + def __repr__(self) -> str: return repr(self._file) - def __iter__(self): + def __iter__(self) -> t.Iterator[t.AnyStr]: return iter(self._file) -def echo(message=None, file=None, nl=True, err=False, color=None): - """Prints a message plus a newline to the given file or stdout. On - first sight, this looks like the print function, but it has improved - support for handling Unicode and binary data that does not fail no - matter how badly configured the system is. +def echo( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.Any]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. - Primarily it means that you can print binary data as well as Unicode - data on both 2.x and 3.x to the given file in the most appropriate way - possible. This is a very carefree function in that it will try its - best to not fail. As of Click 6.0 this includes support for unicode - output on the Windows console. + Compared to :func:`print`, this does the following: - In addition to that, if `colorama`_ is installed, the echo function will - also support clever handling of ANSI codes. Essentially it will then - do the following: + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. - - add transparent handling of ANSI color codes on Windows. - - hide ANSI codes automatically if the destination file is not a - terminal. - - .. _colorama: https://pypi.org/project/colorama/ + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. .. versionchanged:: 6.0 - As of Click 6.0 the echo function will properly support unicode - output on the windows console. Not that click does not modify - the interpreter in any way which means that `sys.stdout` or the - print statement or function will still not provide unicode support. - - .. versionchanged:: 2.0 - Starting with version 2.0 of Click, the echo function will work - with colorama if it's installed. - - .. versionadded:: 3.0 - The `err` parameter was added. + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. .. versionchanged:: 4.0 - Added the `color` flag. + Added the ``color`` parameter. - :param message: the message to print - :param file: the file to write to (defaults to ``stdout``) - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``. This is faster and easier than calling - :func:`get_text_stderr` yourself. - :param nl: if set to `True` (the default) a newline is printed afterwards. - :param color: controls if the terminal supports ANSI colors or not. The - default is autodetection. + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. """ if file is None: if err: @@ -218,70 +254,73 @@ def echo(message=None, file=None, nl=True, err=False, color=None): file = _default_text_stdout() # Convert non bytes/text into the native string type. - if message is not None and not isinstance(message, echo_native_types): - message = text_type(message) + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: t.Optional[t.Union[str, bytes]] = str(message) + else: + out = message if nl: - message = message or u'' - if isinstance(message, text_type): - message += u'\n' + out = out or "" + if isinstance(out, str): + out += "\n" else: - message += b'\n' + out += b"\n" - # If there is a message, and we're in Python 3, and the value looks - # like bytes, we manually need to find the binary stream and write the - # message in there. This is done separately so that most stream - # types will work as you would expect. Eg: you can write to StringIO - # for other cases. - if message and not PY2 and is_bytes(message): + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): binary_file = _find_binary_writer(file) + if binary_file is not None: file.flush() - binary_file.write(message) + binary_file.write(out) binary_file.flush() return - # ANSI-style support. If there is no message or we are dealing with - # bytes nothing is happening. If we are connected to a file we want - # to strip colors. If we are on windows we either wrap the stream - # to strip the color or we use the colorama support to translate the - # ansi codes to API calls. - if message and not is_bytes(message): + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: color = resolve_color_default(color) + if should_strip_ansi(file, color): - message = strip_ansi(message) + out = strip_ansi(out) elif WIN: if auto_wrap_for_ansi is not None: - file = auto_wrap_for_ansi(file) + file = auto_wrap_for_ansi(file) # type: ignore elif not color: - message = strip_ansi(message) + out = strip_ansi(out) - if message: - file.write(message) + file.write(out) # type: ignore file.flush() -def get_binary_stream(name): - """Returns a system stream for byte processing. This essentially - returns the stream from the sys module with the given name but it - solves some compatibility issues between different Python versions. - Primarily this function is necessary for getting binary streams on - Python 3. +def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO: + """Returns a system stream for byte processing. :param name: the name of the stream to open. Valid names are ``'stdin'``, ``'stdout'`` and ``'stderr'`` """ opener = binary_streams.get(name) if opener is None: - raise TypeError('Unknown standard stream %r' % name) + raise TypeError(f"Unknown standard stream '{name}'") return opener() -def get_text_stream(name, encoding=None, errors='strict'): +def get_text_stream( + name: "te.Literal['stdin', 'stdout', 'stderr']", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", +) -> t.TextIO: """Returns a system stream for text processing. This usually returns a wrapped stream around a binary stream returned from - :func:`get_binary_stream` but it also can take shortcuts on Python 3 - for already correctly configured streams. + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. :param name: the name of the stream to open. Valid names are ``'stdin'``, ``'stdout'`` and ``'stderr'`` @@ -290,65 +329,60 @@ def get_text_stream(name, encoding=None, errors='strict'): """ opener = text_streams.get(name) if opener is None: - raise TypeError('Unknown standard stream %r' % name) + raise TypeError(f"Unknown standard stream '{name}'") return opener(encoding, errors) -def open_file(filename, mode='r', encoding=None, errors='strict', - lazy=False, atomic=False): - """This is similar to how the :class:`File` works but for manual - usage. Files are opened non lazy by default. This can open regular - files as well as stdin/stdout if ``'-'`` is passed. +def open_file( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. - If stdin/stdout is returned the stream is wrapped so that the context - manager will not close the stream accidentally. This makes it possible - to always use the function like this without having to worry to - accidentally close a standard stream:: + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python with open_file(filename) as f: ... - .. versionadded:: 3.0 + :param filename: The name of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. - :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). - :param mode: the mode in which to open the file. - :param encoding: the encoding to use. - :param errors: the error handling for this file. - :param lazy: can be flipped to true to open the file lazily. - :param atomic: in atomic mode writes go into a temporary file and it's - moved on close. + .. versionadded:: 3.0 """ if lazy: - return LazyFile(filename, mode, encoding, errors, atomic=atomic) - f, should_close = open_stream(filename, mode, encoding, errors, - atomic=atomic) + return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic)) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + if not should_close: - f = KeepOpenFile(f) + f = t.cast(t.IO, KeepOpenFile(f)) + return f -def get_os_args(): - """This returns the argument part of sys.argv in the most appropriate - form for processing. What this means is that this return value is in - a format that works for Click to process but does not necessarily - correspond well to what's actually standard for the interpreter. - - On most environments the return value is ``sys.argv[:1]`` unchanged. - However if you are on Windows and running Python 2 the return value - will actually be a list of unicode strings instead because the - default behavior on that platform otherwise will not be able to - carry all possible values that sys.argv can have. - - .. versionadded:: 6.0 - """ - # We can only extract the unicode argv if sys.argv has not been - # changed since the startup of the application. - if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): - return _get_windows_argv() - return sys.argv[1:] - - -def format_filename(filename, shorten=False): +def format_filename( + filename: t.Union[str, bytes, os.PathLike], shorten: bool = False +) -> str: """Formats a filename for user display. The main purpose of this function is to ensure that the filename can be displayed at all. This will decode the filename to unicode if necessary in a way that it will @@ -362,10 +396,11 @@ def format_filename(filename, shorten=False): """ if shorten: filename = os.path.basename(filename) - return filename_to_ui(filename) + + return os.fsdecode(filename) -def get_app_dir(app_name, roaming=True, force_posix=False): +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: r"""Returns the config folder for the application. The default behavior is to return whatever is most appropriate for the operating system. @@ -380,13 +415,9 @@ def get_app_dir(app_name, roaming=True, force_posix=False): ``~/.config/foo-bar`` Unix (POSIX): ``~/.foo-bar`` - Win XP (roaming): - ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` - Win XP (not roaming): - ``C:\Documents and Settings\\Application Data\Foo Bar`` - Win 7 (roaming): + Windows (roaming): ``C:\Users\\AppData\Roaming\Foo Bar`` - Win 7 (not roaming): + Windows (not roaming): ``C:\Users\\AppData\Local\Foo Bar`` .. versionadded:: 2.0 @@ -401,22 +432,24 @@ def get_app_dir(app_name, roaming=True, force_posix=False): application support folder. """ if WIN: - key = roaming and 'APPDATA' or 'LOCALAPPDATA' + key = "APPDATA" if roaming else "LOCALAPPDATA" folder = os.environ.get(key) if folder is None: - folder = os.path.expanduser('~') + folder = os.path.expanduser("~") return os.path.join(folder, app_name) if force_posix: - return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) - if sys.platform == 'darwin': - return os.path.join(os.path.expanduser( - '~/Library/Application Support'), app_name) + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) return os.path.join( - os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), - _posixify(app_name)) + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) -class PacifyFlushWrapper(object): +class PacifyFlushWrapper: """This wrapper is used to catch and suppress BrokenPipeErrors resulting from ``.flush()`` being called on broken pipe during the shutdown/final-GC of the Python interpreter. Notably ``.flush()`` is always called on @@ -425,16 +458,123 @@ class PacifyFlushWrapper(object): pipe, all calls and attributes are proxied. """ - def __init__(self, wrapped): + def __init__(self, wrapped: t.IO) -> None: self.wrapped = wrapped - def flush(self): + def flush(self) -> None: try: self.wrapped.flush() - except IOError as e: + except OSError as e: import errno + if e.errno != errno.EPIPE: raise - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> t.Any: return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(_main, "__package__", None) is None or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: t.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> t.List[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/libs/common/configobj/__init__.py b/libs/common/configobj/__init__.py deleted file mode 100644 index 928c2083..00000000 --- a/libs/common/configobj/__init__.py +++ /dev/null @@ -1,2453 +0,0 @@ -# configobj.py -# -*- coding: utf-8 -*- -# pylint: disable=bad-continuation - -"""A config file reader/writer that supports nested sections in config files.""" - -# Copyright (C) 2005-2014: -# (name) : (email) -# Michael Foord: fuzzyman AT voidspace DOT org DOT uk -# Nicola Larosa: nico AT tekNico DOT net -# Rob Dennis: rdennis AT gmail DOT com -# Eli Courtwright: eli AT courtwright DOT org - -# This software is licensed under the terms of the BSD license. -# http://opensource.org/licenses/BSD-3-Clause - -# ConfigObj 5 - main repository for documentation and issue tracking: -# https://github.com/DiffSK/configobj - -import os -import re -import sys -import copy - -from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE - -try: - # Python 3 - from collections.abc import Mapping -except ImportError: - # Python 2.7 - from collections import Mapping - -import six -from ._version import __version__ - -# imported lazily to avoid startup performance hit if it isn't used -compiler = None - -# A dictionary mapping BOM to -# the encoding to decode with, and what to set the -# encoding attribute to. -BOMS = { - BOM_UTF8: ('utf_8', None), - BOM_UTF16_BE: ('utf16_be', 'utf_16'), - BOM_UTF16_LE: ('utf16_le', 'utf_16'), - BOM_UTF16: ('utf_16', 'utf_16'), - } -# All legal variants of the BOM codecs. -# TODO: the list of aliases is not meant to be exhaustive, is there a -# better way ? -BOM_LIST = { - 'utf_16': 'utf_16', - 'u16': 'utf_16', - 'utf16': 'utf_16', - 'utf-16': 'utf_16', - 'utf16_be': 'utf16_be', - 'utf_16_be': 'utf16_be', - 'utf-16be': 'utf16_be', - 'utf16_le': 'utf16_le', - 'utf_16_le': 'utf16_le', - 'utf-16le': 'utf16_le', - 'utf_8': 'utf_8', - 'u8': 'utf_8', - 'utf': 'utf_8', - 'utf8': 'utf_8', - 'utf-8': 'utf_8', - } - -# Map of encodings to the BOM to write. -BOM_SET = { - 'utf_8': BOM_UTF8, - 'utf_16': BOM_UTF16, - 'utf16_be': BOM_UTF16_BE, - 'utf16_le': BOM_UTF16_LE, - None: BOM_UTF8 - } - - -def match_utf8(encoding): - return BOM_LIST.get(encoding.lower()) == 'utf_8' - - -# Quote strings used for writing values -squot = "'%s'" -dquot = '"%s"' -noquot = "%s" -wspace_plus = ' \r\n\v\t\'"' -tsquot = '"""%s"""' -tdquot = "'''%s'''" - -# Sentinel for use in getattr calls to replace hasattr -MISSING = object() - -__all__ = ( - 'DEFAULT_INDENT_TYPE', - 'DEFAULT_INTERPOLATION', - 'ConfigObjError', - 'NestingError', - 'ParseError', - 'DuplicateError', - 'ConfigspecError', - 'ConfigObj', - 'SimpleVal', - 'InterpolationError', - 'InterpolationLoopError', - 'MissingInterpolationOption', - 'RepeatSectionError', - 'ReloadError', - 'UnreprError', - 'UnknownType', - 'flatten_errors', - 'get_extra_values' -) - -DEFAULT_INTERPOLATION = 'configparser' -DEFAULT_INDENT_TYPE = ' ' -MAX_INTERPOL_DEPTH = 10 - -OPTION_DEFAULTS = { - 'interpolation': True, - 'raise_errors': False, - 'list_values': True, - 'create_empty': False, - 'file_error': False, - 'configspec': None, - 'stringify': True, - # option may be set to one of ('', ' ', '\t') - 'indent_type': None, - 'encoding': None, - 'default_encoding': None, - 'unrepr': False, - 'write_empty_values': False, -} - -# this could be replaced if six is used for compatibility, or there are no -# more assertions about items being a string - - -def getObj(s): - global compiler - if compiler is None: - import compiler - s = "a=" + s - p = compiler.parse(s) - return p.getChildren()[1].getChildren()[0].getChildren()[1] - - -class UnknownType(Exception): - pass - - -def unrepr(s): - if not s: - return s - - # this is supposed to be safe - import ast - return ast.literal_eval(s) - - -class ConfigObjError(SyntaxError): - """ - This is the base class for all errors that ConfigObj raises. - It is a subclass of SyntaxError. - """ - def __init__(self, message='', line_number=None, line=''): - self.line = line - self.line_number = line_number - SyntaxError.__init__(self, message) - - -class NestingError(ConfigObjError): - """ - This error indicates a level of nesting that doesn't match. - """ - - -class ParseError(ConfigObjError): - """ - This error indicates that a line is badly written. - It is neither a valid ``key = value`` line, - nor a valid section marker line. - """ - - -class ReloadError(IOError): - """ - A 'reload' operation failed. - This exception is a subclass of ``IOError``. - """ - def __init__(self): - IOError.__init__(self, 'reload failed, filename is not set.') - - -class DuplicateError(ConfigObjError): - """ - The keyword or section specified already exists. - """ - - -class ConfigspecError(ConfigObjError): - """ - An error occured whilst parsing a configspec. - """ - - -class InterpolationError(ConfigObjError): - """Base class for the two interpolation errors.""" - - -class InterpolationLoopError(InterpolationError): - """Maximum interpolation depth exceeded in string interpolation.""" - - def __init__(self, option): - InterpolationError.__init__( - self, - 'interpolation loop detected in value "%s".' % option) - - -class RepeatSectionError(ConfigObjError): - """ - This error indicates additional sections in a section with a - ``__many__`` (repeated) section. - """ - - -class MissingInterpolationOption(InterpolationError): - """A value specified for interpolation was missing.""" - def __init__(self, option): - msg = 'missing option "%s" in interpolation.' % option - InterpolationError.__init__(self, msg) - - -class UnreprError(ConfigObjError): - """An error parsing in unrepr mode.""" - - - -class InterpolationEngine(object): - """ - A helper class to help perform string interpolation. - - This class is an abstract base class; its descendants perform - the actual work. - """ - - # compiled regexp to use in self.interpolate() - _KEYCRE = re.compile(r"%\(([^)]*)\)s") - _cookie = '%' - - def __init__(self, section): - # the Section instance that "owns" this engine - self.section = section - - - def interpolate(self, key, value): - # short-cut - if not self._cookie in value: - return value - - def recursive_interpolate(key, value, section, backtrail): - """The function that does the actual work. - - ``value``: the string we're trying to interpolate. - ``section``: the section in which that string was found - ``backtrail``: a dict to keep track of where we've been, - to detect and prevent infinite recursion loops - - This is similar to a depth-first-search algorithm. - """ - # Have we been here already? - if (key, section.name) in backtrail: - # Yes - infinite loop detected - raise InterpolationLoopError(key) - # Place a marker on our backtrail so we won't come back here again - backtrail[(key, section.name)] = 1 - - # Now start the actual work - match = self._KEYCRE.search(value) - while match: - # The actual parsing of the match is implementation-dependent, - # so delegate to our helper function - k, v, s = self._parse_match(match) - if k is None: - # That's the signal that no further interpolation is needed - replacement = v - else: - # Further interpolation may be needed to obtain final value - replacement = recursive_interpolate(k, v, s, backtrail) - # Replace the matched string with its final value - start, end = match.span() - value = ''.join((value[:start], replacement, value[end:])) - new_search_start = start + len(replacement) - # Pick up the next interpolation key, if any, for next time - # through the while loop - match = self._KEYCRE.search(value, new_search_start) - - # Now safe to come back here again; remove marker from backtrail - del backtrail[(key, section.name)] - - return value - - # Back in interpolate(), all we have to do is kick off the recursive - # function with appropriate starting values - value = recursive_interpolate(key, value, self.section, {}) - return value - - - def _fetch(self, key): - """Helper function to fetch values from owning section. - - Returns a 2-tuple: the value, and the section where it was found. - """ - # switch off interpolation before we try and fetch anything ! - save_interp = self.section.main.interpolation - self.section.main.interpolation = False - - # Start at section that "owns" this InterpolationEngine - current_section = self.section - while True: - # try the current section first - val = current_section.get(key) - if val is not None and not isinstance(val, Section): - break - # try "DEFAULT" next - val = current_section.get('DEFAULT', {}).get(key) - if val is not None and not isinstance(val, Section): - break - # move up to parent and try again - # top-level's parent is itself - if current_section.parent is current_section: - # reached top level, time to give up - break - current_section = current_section.parent - - # restore interpolation to previous value before returning - self.section.main.interpolation = save_interp - if val is None: - raise MissingInterpolationOption(key) - return val, current_section - - - def _parse_match(self, match): - """Implementation-dependent helper function. - - Will be passed a match object corresponding to the interpolation - key we just found (e.g., "%(foo)s" or "$foo"). Should look up that - key in the appropriate config file section (using the ``_fetch()`` - helper function) and return a 3-tuple: (key, value, section) - - ``key`` is the name of the key we're looking for - ``value`` is the value found for that key - ``section`` is a reference to the section where it was found - - ``key`` and ``section`` should be None if no further - interpolation should be performed on the resulting value - (e.g., if we interpolated "$$" and returned "$"). - """ - raise NotImplementedError() - - - -class ConfigParserInterpolation(InterpolationEngine): - """Behaves like ConfigParser.""" - _cookie = '%' - _KEYCRE = re.compile(r"%\(([^)]*)\)s") - - def _parse_match(self, match): - key = match.group(1) - value, section = self._fetch(key) - return key, value, section - - - -class TemplateInterpolation(InterpolationEngine): - """Behaves like string.Template.""" - _cookie = '$' - _delimiter = '$' - _KEYCRE = re.compile(r""" - \$(?: - (?P\$) | # Two $ signs - (?P[_a-z][_a-z0-9]*) | # $name format - {(?P[^}]*)} # ${name} format - ) - """, re.IGNORECASE | re.VERBOSE) - - def _parse_match(self, match): - # Valid name (in or out of braces): fetch value from section - key = match.group('named') or match.group('braced') - if key is not None: - value, section = self._fetch(key) - return key, value, section - # Escaped delimiter (e.g., $$): return single delimiter - if match.group('escaped') is not None: - # Return None for key and section to indicate it's time to stop - return None, self._delimiter, None - # Anything else: ignore completely, just return it unchanged - return None, match.group(), None - - -interpolation_engines = { - 'configparser': ConfigParserInterpolation, - 'template': TemplateInterpolation, -} - - -def __newobj__(cls, *args): - # Hack for pickle - return cls.__new__(cls, *args) - -class Section(dict): - """ - A dictionary-like object that represents a section in a config file. - - It does string interpolation if the 'interpolation' attribute - of the 'main' object is set to True. - - Interpolation is tried first from this object, then from the 'DEFAULT' - section of this object, next from the parent and its 'DEFAULT' section, - and so on until the main object is reached. - - A Section will behave like an ordered dictionary - following the - order of the ``scalars`` and ``sections`` attributes. - You can use this to change the order of members. - - Iteration follows the order: scalars, then sections. - """ - - - def __setstate__(self, state): - dict.update(self, state[0]) - self.__dict__.update(state[1]) - - def __reduce__(self): - state = (dict(self), self.__dict__) - return (__newobj__, (self.__class__,), state) - - - def __init__(self, parent, depth, main, indict=None, name=None): - """ - * parent is the section above - * depth is the depth level of this section - * main is the main ConfigObj - * indict is a dictionary to initialise the section with - """ - if indict is None: - indict = {} - dict.__init__(self) - # used for nesting level *and* interpolation - self.parent = parent - # used for the interpolation attribute - self.main = main - # level of nesting depth of this Section - self.depth = depth - # purely for information - self.name = name - # - self._initialise() - # we do this explicitly so that __setitem__ is used properly - # (rather than just passing to ``dict.__init__``) - for entry, value in indict.items(): - self[entry] = value - - - def _initialise(self): - # the sequence of scalar values in this Section - self.scalars = [] - # the sequence of sections in this Section - self.sections = [] - # for comments :-) - self.comments = {} - self.inline_comments = {} - # the configspec - self.configspec = None - # for defaults - self.defaults = [] - self.default_values = {} - self.extra_values = [] - self._created = False - - - def _interpolate(self, key, value): - try: - # do we already have an interpolation engine? - engine = self._interpolation_engine - except AttributeError: - # not yet: first time running _interpolate(), so pick the engine - name = self.main.interpolation - if name == True: # note that "if name:" would be incorrect here - # backwards-compatibility: interpolation=True means use default - name = DEFAULT_INTERPOLATION - name = name.lower() # so that "Template", "template", etc. all work - class_ = interpolation_engines.get(name, None) - if class_ is None: - # invalid value for self.main.interpolation - self.main.interpolation = False - return value - else: - # save reference to engine so we don't have to do this again - engine = self._interpolation_engine = class_(self) - # let the engine do the actual work - return engine.interpolate(key, value) - - - def __getitem__(self, key): - """Fetch the item and do string interpolation.""" - val = dict.__getitem__(self, key) - if self.main.interpolation: - if isinstance(val, six.string_types): - return self._interpolate(key, val) - if isinstance(val, list): - def _check(entry): - if isinstance(entry, six.string_types): - return self._interpolate(key, entry) - return entry - new = [_check(entry) for entry in val] - if new != val: - return new - return val - - - def __setitem__(self, key, value, unrepr=False): - """ - Correctly set a value. - - Making dictionary values Section instances. - (We have to special case 'Section' instances - which are also dicts) - - Keys must be strings. - Values need only be strings (or lists of strings) if - ``main.stringify`` is set. - - ``unrepr`` must be set when setting a value to a dictionary, without - creating a new sub-section. - """ - if not isinstance(key, six.string_types): - raise ValueError('The key "%s" is not a string.' % key) - - # add the comment - if key not in self.comments: - self.comments[key] = [] - self.inline_comments[key] = '' - # remove the entry from defaults - if key in self.defaults: - self.defaults.remove(key) - # - if isinstance(value, Section): - if key not in self: - self.sections.append(key) - dict.__setitem__(self, key, value) - elif isinstance(value, Mapping) and not unrepr: - # First create the new depth level, - # then create the section - if key not in self: - self.sections.append(key) - new_depth = self.depth + 1 - dict.__setitem__( - self, - key, - Section( - self, - new_depth, - self.main, - indict=value, - name=key)) - else: - if key not in self: - self.scalars.append(key) - if not self.main.stringify: - if isinstance(value, six.string_types): - pass - elif isinstance(value, (list, tuple)): - for entry in value: - if not isinstance(entry, six.string_types): - raise TypeError('Value is not a string "%s".' % entry) - else: - raise TypeError('Value is not a string "%s".' % value) - dict.__setitem__(self, key, value) - - - def __delitem__(self, key): - """Remove items from the sequence when deleting.""" - dict. __delitem__(self, key) - if key in self.scalars: - self.scalars.remove(key) - else: - self.sections.remove(key) - del self.comments[key] - del self.inline_comments[key] - - - def get(self, key, default=None): - """A version of ``get`` that doesn't bypass string interpolation.""" - try: - return self[key] - except KeyError: - return default - - - def update(self, indict): - """ - A version of update that uses our ``__setitem__``. - """ - for entry in indict: - self[entry] = indict[entry] - - - def pop(self, key, default=MISSING): - """ - 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised' - """ - try: - val = self[key] - except KeyError: - if default is MISSING: - raise - val = default - else: - del self[key] - return val - - - def popitem(self): - """Pops the first (key,val)""" - sequence = (self.scalars + self.sections) - if not sequence: - raise KeyError(": 'popitem(): dictionary is empty'") - key = sequence[0] - val = self[key] - del self[key] - return key, val - - - def clear(self): - """ - A version of clear that also affects scalars/sections - Also clears comments and configspec. - - Leaves other attributes alone : - depth/main/parent are not affected - """ - dict.clear(self) - self.scalars = [] - self.sections = [] - self.comments = {} - self.inline_comments = {} - self.configspec = None - self.defaults = [] - self.extra_values = [] - - - def setdefault(self, key, default=None): - """A version of setdefault that sets sequence if appropriate.""" - try: - return self[key] - except KeyError: - self[key] = default - return self[key] - - - def items(self): - """D.items() -> list of D's (key, value) pairs, as 2-tuples""" - return [(key, self[key]) for key in self.keys()] - - - def keys(self): - """D.keys() -> list of D's keys""" - return self.scalars + self.sections - - - def values(self): - """D.values() -> list of D's values""" - return [self[key] for key in self.keys()] - - - def iteritems(self): - """D.iteritems() -> an iterator over the (key, value) items of D""" - return iter(self.items()) - - - def iterkeys(self): - """D.iterkeys() -> an iterator over the keys of D""" - return iter(self.keys()) - - __iter__ = iterkeys - - - def itervalues(self): - """D.itervalues() -> an iterator over the values of D""" - return iter(self.values()) - - - def __repr__(self): - """x.__repr__() <==> repr(x)""" - def _getval(key): - try: - return self[key] - except MissingInterpolationOption: - return dict.__getitem__(self, key) - return '{%s}' % ', '.join([('{}: {}'.format(repr(key), repr(_getval(key)))) - for key in (self.scalars + self.sections)]) - - __str__ = __repr__ - __str__.__doc__ = "x.__str__() <==> str(x)" - - - # Extra methods - not in a normal dictionary - - def dict(self): - """ - Return a deepcopy of self as a dictionary. - - All members that are ``Section`` instances are recursively turned to - ordinary dictionaries - by calling their ``dict`` method. - - >>> n = a.dict() - >>> n == a - 1 - >>> n is a - 0 - """ - newdict = {} - for entry in self: - this_entry = self[entry] - if isinstance(this_entry, Section): - this_entry = this_entry.dict() - elif isinstance(this_entry, list): - # create a copy rather than a reference - this_entry = list(this_entry) - elif isinstance(this_entry, tuple): - # create a copy rather than a reference - this_entry = tuple(this_entry) - newdict[entry] = this_entry - return newdict - - - def merge(self, indict, decoupled=False): - """ - A recursive update - useful for merging config files. - - Note: if ``decoupled`` is ``True``, then the target object (self) - gets its own copy of any mutable objects in the source dictionary - (both sections and values), paid for by more work for ``merge()`` - and more memory usage. - - >>> a = '''[section1] - ... option1 = True - ... [[subsection]] - ... more_options = False - ... # end of file'''.splitlines() - >>> b = '''# File is user.ini - ... [section1] - ... option1 = False - ... # end of file'''.splitlines() - >>> c1 = ConfigObj(b) - >>> c2 = ConfigObj(a) - >>> c2.merge(c1) - >>> c2 - ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) - """ - for key, val in indict.items(): - if decoupled: - val = copy.deepcopy(val) - if (key in self and isinstance(self[key], Mapping) and - isinstance(val, Mapping)): - self[key].merge(val, decoupled=decoupled) - else: - self[key] = val - - - def rename(self, oldkey, newkey): - """ - Change a keyname to another, without changing position in sequence. - - Implemented so that transformations can be made on keys, - as well as on values. (used by encode and decode) - - Also renames comments. - """ - if oldkey in self.scalars: - the_list = self.scalars - elif oldkey in self.sections: - the_list = self.sections - else: - raise KeyError('Key "%s" not found.' % oldkey) - pos = the_list.index(oldkey) - # - val = self[oldkey] - dict.__delitem__(self, oldkey) - dict.__setitem__(self, newkey, val) - the_list.remove(oldkey) - the_list.insert(pos, newkey) - comm = self.comments[oldkey] - inline_comment = self.inline_comments[oldkey] - del self.comments[oldkey] - del self.inline_comments[oldkey] - self.comments[newkey] = comm - self.inline_comments[newkey] = inline_comment - - - def walk(self, function, raise_errors=True, - call_on_sections=False, **keywargs): - """ - Walk every member and call a function on the keyword and value. - - Return a dictionary of the return values - - If the function raises an exception, raise the errror - unless ``raise_errors=False``, in which case set the return value to - ``False``. - - Any unrecognised keyword arguments you pass to walk, will be pased on - to the function you pass in. - - Note: if ``call_on_sections`` is ``True`` then - on encountering a - subsection, *first* the function is called for the *whole* subsection, - and then recurses into it's members. This means your function must be - able to handle strings, dictionaries and lists. This allows you - to change the key of subsections as well as for ordinary members. The - return value when called on the whole subsection has to be discarded. - - See the encode and decode methods for examples, including functions. - - .. admonition:: caution - - You can use ``walk`` to transform the names of members of a section - but you mustn't add or delete members. - - >>> config = '''[XXXXsection] - ... XXXXkey = XXXXvalue'''.splitlines() - >>> cfg = ConfigObj(config) - >>> cfg - ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) - >>> def transform(section, key): - ... val = section[key] - ... newkey = key.replace('XXXX', 'CLIENT1') - ... section.rename(key, newkey) - ... if isinstance(val, (tuple, list, dict)): - ... pass - ... else: - ... val = val.replace('XXXX', 'CLIENT1') - ... section[newkey] = val - >>> cfg.walk(transform, call_on_sections=True) - {'CLIENT1section': {'CLIENT1key': None}} - >>> cfg - ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) - """ - out = {} - # scalars first - for i in range(len(self.scalars)): - entry = self.scalars[i] - try: - val = function(self, entry, **keywargs) - # bound again in case name has changed - entry = self.scalars[i] - out[entry] = val - except Exception: - if raise_errors: - raise - else: - entry = self.scalars[i] - out[entry] = False - # then sections - for i in range(len(self.sections)): - entry = self.sections[i] - if call_on_sections: - try: - function(self, entry, **keywargs) - except Exception: - if raise_errors: - raise - else: - entry = self.sections[i] - out[entry] = False - # bound again in case name has changed - entry = self.sections[i] - # previous result is discarded - out[entry] = self[entry].walk( - function, - raise_errors=raise_errors, - call_on_sections=call_on_sections, - **keywargs) - return out - - - def as_bool(self, key): - """ - Accepts a key as input. The corresponding value must be a string or - the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to - retain compatibility with Python 2.2. - - If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns - ``True``. - - If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns - ``False``. - - ``as_bool`` is not case sensitive. - - Any other input will raise a ``ValueError``. - - >>> a = ConfigObj() - >>> a['a'] = 'fish' - >>> a.as_bool('a') - Traceback (most recent call last): - ValueError: Value "fish" is neither True nor False - >>> a['b'] = 'True' - >>> a.as_bool('b') - 1 - >>> a['b'] = 'off' - >>> a.as_bool('b') - 0 - """ - val = self[key] - if val == True: - return True - elif val == False: - return False - else: - try: - if not isinstance(val, six.string_types): - # TODO: Why do we raise a KeyError here? - raise KeyError() - else: - return self.main._bools[val.lower()] - except KeyError: - raise ValueError('Value "%s" is neither True nor False' % val) - - - def as_int(self, key): - """ - A convenience method which coerces the specified value to an integer. - - If the value is an invalid literal for ``int``, a ``ValueError`` will - be raised. - - >>> a = ConfigObj() - >>> a['a'] = 'fish' - >>> a.as_int('a') - Traceback (most recent call last): - ValueError: invalid literal for int() with base 10: 'fish' - >>> a['b'] = '1' - >>> a.as_int('b') - 1 - >>> a['b'] = '3.2' - >>> a.as_int('b') - Traceback (most recent call last): - ValueError: invalid literal for int() with base 10: '3.2' - """ - return int(self[key]) - - - def as_float(self, key): - """ - A convenience method which coerces the specified value to a float. - - If the value is an invalid literal for ``float``, a ``ValueError`` will - be raised. - - >>> a = ConfigObj() - >>> a['a'] = 'fish' - >>> a.as_float('a') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ValueError: invalid literal for float(): fish - >>> a['b'] = '1' - >>> a.as_float('b') - 1.0 - >>> a['b'] = '3.2' - >>> a.as_float('b') #doctest: +ELLIPSIS - 3.2... - """ - return float(self[key]) - - - def as_list(self, key): - """ - A convenience method which fetches the specified value, guaranteeing - that it is a list. - - >>> a = ConfigObj() - >>> a['a'] = 1 - >>> a.as_list('a') - [1] - >>> a['a'] = (1,) - >>> a.as_list('a') - [1] - >>> a['a'] = [1] - >>> a.as_list('a') - [1] - """ - result = self[key] - if isinstance(result, (tuple, list)): - return list(result) - return [result] - - - def restore_default(self, key): - """ - Restore (and return) default value for the specified key. - - This method will only work for a ConfigObj that was created - with a configspec and has been validated. - - If there is no default value for this key, ``KeyError`` is raised. - """ - default = self.default_values[key] - dict.__setitem__(self, key, default) - if key not in self.defaults: - self.defaults.append(key) - return default - - - def restore_defaults(self): - """ - Recursively restore default values to all members - that have them. - - This method will only work for a ConfigObj that was created - with a configspec and has been validated. - - It doesn't delete or modify entries without default values. - """ - for key in self.default_values: - self.restore_default(key) - - for section in self.sections: - self[section].restore_defaults() - - -def _get_triple_quote(value): - """Helper for triple-quoting round-trips.""" - if ('"""' in value) and ("'''" in value): - raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) - - return tsquot if "'''" in value else tdquot - - -class ConfigObj(Section): - """An object to read, create, and write config files.""" - - MAX_PARSE_ERROR_DETAILS = 5 - - # Override/append to this class variable for alternative comment markers - # TODO: also support inline comments (needs dynamic compiling of the regex below) - COMMENT_MARKERS = ['#'] - - _keyword = re.compile(r'''^ # line start - (\s*) # indentation - ( # keyword - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'"=].*?) # no quotes - ) - \s*=\s* # divider - (.*) # value (including list values and comments) - $ # line end - ''', - re.VERBOSE) - - _sectionmarker = re.compile(r'''^ - (\s*) # 1: indentation - ((?:\[\s*)+) # 2: section marker open - ( # 3: section name open - (?:"\s*\S.*?\s*")| # at least one non-space with double quotes - (?:'\s*\S.*?\s*')| # at least one non-space with single quotes - (?:[^'"\s].*?) # at least one non-space unquoted - ) # section name close - ((?:\s*\])+) # 4: section marker close - (\s*(?:\#.*)?)? # 5: optional comment - $''', - re.VERBOSE) - - # this regexp pulls list values out as a single string - # or single values and comments - # FIXME: this regex adds a '' to the end of comma terminated lists - # workaround in ``_handle_value`` - _valueexp = re.compile(r'''^ - (?: - (?: - ( - (?: - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\#][^,\#]*?) # unquoted - ) - \s*,\s* # comma - )* # match all list items ending in a comma (if any) - ) - ( - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\#\s][^,]*?)| # unquoted - (?:(? 1: - msg = ["Parsing failed with {} errors.".format(len(self._errors))] - for error in self._errors[:self.MAX_PARSE_ERROR_DETAILS]: - msg.append(str(error)) - if len(self._errors) > self.MAX_PARSE_ERROR_DETAILS: - msg.append("{} more error(s)!" - .format(len(self._errors) - self.MAX_PARSE_ERROR_DETAILS)) - error = ConfigObjError('\n '.join(msg)) - else: - error = self._errors[0] - # set the errors attribute; it's a list of tuples: - # (error_type, message, line_number) - error.errors = self._errors - # set the config attribute - error.config = self - raise error - # delete private attributes - del self._errors - - if configspec is None: - self.configspec = None - else: - self._handle_configspec(configspec) - - - def _initialise(self, options=None): - if options is None: - options = OPTION_DEFAULTS - - # initialise a few variables - self.filename = None - self._errors = [] - self.raise_errors = options['raise_errors'] - self.interpolation = options['interpolation'] - self.list_values = options['list_values'] - self.create_empty = options['create_empty'] - self.file_error = options['file_error'] - self.stringify = options['stringify'] - self.indent_type = options['indent_type'] - self.encoding = options['encoding'] - self.default_encoding = options['default_encoding'] - self.BOM = False - self.newlines = None - self.write_empty_values = options['write_empty_values'] - self.unrepr = options['unrepr'] - - self.initial_comment = [] - self.final_comment = [] - self.configspec = None - - if self._inspec: - self.list_values = False - - # Clear section attributes as well - Section._initialise(self) - - - def __repr__(self): - def _getval(key): - try: - return self[key] - except MissingInterpolationOption: - return dict.__getitem__(self, key) - return ('{}({{{}}})'.format(self.__class__.__name__, - ', '.join([('{}: {}'.format(repr(key), repr(_getval(key)))) - for key in (self.scalars + self.sections)]))) - - - def _handle_bom(self, infile): - """ - Handle any BOM, and decode if necessary. - - If an encoding is specified, that *must* be used - but the BOM should - still be removed (and the BOM attribute set). - - (If the encoding is wrongly specified, then a BOM for an alternative - encoding won't be discovered or removed.) - - If an encoding is not specified, UTF8 or UTF16 BOM will be detected and - removed. The BOM attribute will be set. UTF16 will be decoded to - unicode. - - NOTE: This method must not be called with an empty ``infile``. - - Specifying the *wrong* encoding is likely to cause a - ``UnicodeDecodeError``. - - ``infile`` must always be returned as a list of lines, but may be - passed in as a single string. - """ - - if ((self.encoding is not None) and - (self.encoding.lower() not in BOM_LIST)): - # No need to check for a BOM - # the encoding specified doesn't have one - # just decode - return self._decode(infile, self.encoding) - - if isinstance(infile, (list, tuple)): - line = infile[0] - else: - line = infile - - if isinstance(line, six.text_type): - # it's already decoded and there's no need to do anything - # else, just use the _decode utility method to handle - # listifying appropriately - return self._decode(infile, self.encoding) - - if self.encoding is not None: - # encoding explicitly supplied - # And it could have an associated BOM - # TODO: if encoding is just UTF16 - we ought to check for both - # TODO: big endian and little endian versions. - enc = BOM_LIST[self.encoding.lower()] - if enc == 'utf_16': - # For UTF16 we try big endian and little endian - for BOM, (encoding, final_encoding) in list(BOMS.items()): - if not final_encoding: - # skip UTF8 - continue - if infile.startswith(BOM): - ### BOM discovered - ##self.BOM = True - # Don't need to remove BOM - return self._decode(infile, encoding) - - # If we get this far, will *probably* raise a DecodeError - # As it doesn't appear to start with a BOM - return self._decode(infile, self.encoding) - - # Must be UTF8 - BOM = BOM_SET[enc] - if not line.startswith(BOM): - return self._decode(infile, self.encoding) - - newline = line[len(BOM):] - - # BOM removed - if isinstance(infile, (list, tuple)): - infile[0] = newline - else: - infile = newline - self.BOM = True - return self._decode(infile, self.encoding) - - # No encoding specified - so we need to check for UTF8/UTF16 - for BOM, (encoding, final_encoding) in list(BOMS.items()): - if not isinstance(line, six.binary_type) or not line.startswith(BOM): - # didn't specify a BOM, or it's not a bytestring - continue - else: - # BOM discovered - self.encoding = final_encoding - if not final_encoding: - self.BOM = True - # UTF8 - # remove BOM - newline = line[len(BOM):] - if isinstance(infile, (list, tuple)): - infile[0] = newline - else: - infile = newline - # UTF-8 - if isinstance(infile, six.text_type): - return infile.splitlines(True) - elif isinstance(infile, six.binary_type): - return infile.decode('utf-8').splitlines(True) - else: - return self._decode(infile, 'utf-8') - # UTF16 - have to decode - return self._decode(infile, encoding) - - - if six.PY2 and isinstance(line, str): - # don't actually do any decoding, since we're on python 2 and - # returning a bytestring is fine - return self._decode(infile, None) - # No BOM discovered and no encoding specified, default to UTF-8 - if isinstance(infile, six.binary_type): - return infile.decode('utf-8').splitlines(True) - else: - return self._decode(infile, 'utf-8') - - - def _decode(self, infile, encoding): - """ - Decode infile to unicode. Using the specified encoding. - - if is a string, it also needs converting to a list. - """ - if isinstance(infile, six.binary_type): - # NOTE: Could raise a ``UnicodeDecodeError`` - if encoding: - return infile.decode(encoding).splitlines(True) - else: - return infile.splitlines(True) - if isinstance(infile, six.string_types): - return infile.splitlines(True) - - if encoding: - for i, line in enumerate(infile): - if isinstance(line, six.binary_type): - # NOTE: The isinstance test here handles mixed lists of unicode/string - # NOTE: But the decode will break on any non-string values - # NOTE: Or could raise a ``UnicodeDecodeError`` - infile[i] = line.decode(encoding) - return infile - - - def _decode_element(self, line): - """Decode element to unicode if necessary.""" - if isinstance(line, six.binary_type) and self.default_encoding: - return line.decode(self.default_encoding) - else: - return line - - - # TODO: this may need to be modified - def _str(self, value): - """ - Used by ``stringify`` within validate, to turn non-string values - into strings. - """ - if not isinstance(value, six.string_types): - # intentially 'str' because it's just whatever the "normal" - # string type is for the python version we're dealing with - return str(value) - else: - return value - - - def _parse(self, infile): - """Actually parse the config file.""" - temp_list_values = self.list_values - if self.unrepr: - self.list_values = False - - comment_list = [] - done_start = False - this_section = self - maxline = len(infile) - 1 - cur_index = -1 - reset_comment = False - comment_markers = tuple(self.COMMENT_MARKERS) - - while cur_index < maxline: - if reset_comment: - comment_list = [] - cur_index += 1 - line = infile[cur_index] - sline = line.strip() - # do we have anything on the line ? - if not sline or sline.startswith(comment_markers): - reset_comment = False - comment_list.append(line) - continue - - if not done_start: - # preserve initial comment - self.initial_comment = comment_list - comment_list = [] - done_start = True - - reset_comment = True - # first we check if it's a section marker - mat = self._sectionmarker.match(line) - if mat is not None: - # is a section line - (indent, sect_open, sect_name, sect_close, comment) = mat.groups() - if indent and (self.indent_type is None): - self.indent_type = indent - cur_depth = sect_open.count('[') - if cur_depth != sect_close.count(']'): - self._handle_error("Cannot compute the section depth", - NestingError, infile, cur_index) - continue - - if cur_depth < this_section.depth: - # the new section is dropping back to a previous level - try: - parent = self._match_depth(this_section, - cur_depth).parent - except SyntaxError: - self._handle_error("Cannot compute nesting level", - NestingError, infile, cur_index) - continue - elif cur_depth == this_section.depth: - # the new section is a sibling of the current section - parent = this_section.parent - elif cur_depth == this_section.depth + 1: - # the new section is a child the current section - parent = this_section - else: - self._handle_error("Section too nested", - NestingError, infile, cur_index) - continue - - sect_name = self._unquote(sect_name) - if sect_name in parent: - self._handle_error('Duplicate section name', - DuplicateError, infile, cur_index) - continue - - # create the new section - this_section = Section( - parent, - cur_depth, - self, - name=sect_name) - parent[sect_name] = this_section - parent.inline_comments[sect_name] = comment - parent.comments[sect_name] = comment_list - continue - # - # it's not a section marker, - # so it should be a valid ``key = value`` line - mat = self._keyword.match(line) - if mat is None: - self._handle_error( - 'Invalid line ({!r}) (matched as neither section nor keyword)'.format(line), - ParseError, infile, cur_index) - else: - # is a keyword value - # value will include any inline comment - (indent, key, value) = mat.groups() - if indent and (self.indent_type is None): - self.indent_type = indent - # check for a multiline value - if value[:3] in ['"""', "'''"]: - try: - value, comment, cur_index = self._multiline( - value, infile, cur_index, maxline) - except SyntaxError: - self._handle_error( - 'Parse error in multiline value', - ParseError, infile, cur_index) - continue - else: - if self.unrepr: - comment = '' - try: - value = unrepr(value) - except Exception as cause: - if isinstance(cause, UnknownType): - msg = 'Unknown name or type in value' - else: - msg = 'Parse error from unrepr-ing multiline value' - self._handle_error(msg, UnreprError, infile, cur_index) - continue - else: - if self.unrepr: - comment = '' - try: - value = unrepr(value) - except Exception as cause: - if isinstance(cause, UnknownType): - msg = 'Unknown name or type in value' - else: - msg = 'Parse error from unrepr-ing value' - self._handle_error(msg, UnreprError, infile, cur_index) - continue - else: - # extract comment and lists - try: - (value, comment) = self._handle_value(value) - except SyntaxError: - self._handle_error( - 'Parse error in value', - ParseError, infile, cur_index) - continue - # - key = self._unquote(key) - if key in this_section: - self._handle_error( - 'Duplicate keyword name', - DuplicateError, infile, cur_index) - continue - # add the key. - # we set unrepr because if we have got this far we will never - # be creating a new section - this_section.__setitem__(key, value, unrepr=True) - this_section.inline_comments[key] = comment - this_section.comments[key] = comment_list - continue - # - if self.indent_type is None: - # no indentation used, set the type accordingly - self.indent_type = '' - - # preserve the final comment - if not self and not self.initial_comment: - self.initial_comment = comment_list - elif not reset_comment: - self.final_comment = comment_list - self.list_values = temp_list_values - - - def _match_depth(self, sect, depth): - """ - Given a section and a depth level, walk back through the sections - parents to see if the depth level matches a previous section. - - Return a reference to the right section, - or raise a SyntaxError. - """ - while depth < sect.depth: - if sect is sect.parent: - # we've reached the top level already - raise SyntaxError() - sect = sect.parent - if sect.depth == depth: - return sect - # shouldn't get here - raise SyntaxError() - - - def _handle_error(self, text, ErrorClass, infile, cur_index): - """ - Handle an error according to the error settings. - - Either raise the error or store it. - The error will have occured at ``cur_index`` - """ - line = infile[cur_index] - cur_index += 1 - message = '{} at line {}.'.format(text, cur_index) - error = ErrorClass(message, cur_index, line) - if self.raise_errors: - # raise the error - parsing stops here - raise error - # store the error - # reraise when parsing has finished - self._errors.append(error) - - - def _unquote(self, value): - """Return an unquoted version of a value""" - if not value: - # should only happen during parsing of lists - raise SyntaxError - if (value[0] == value[-1]) and (value[0] in ('"', "'")): - value = value[1:-1] - return value - - - def _quote(self, value, multiline=True): - """ - Return a safely quoted version of a value. - - Raise a ConfigObjError if the value cannot be safely quoted. - If multiline is ``True`` (default) then use triple quotes - if necessary. - - * Don't quote values that don't need it. - * Recursively quote members of a list and return a comma joined list. - * Multiline is ``False`` for lists. - * Obey list syntax for empty and single member lists. - - If ``list_values=False`` then the value is only quoted if it contains - a ``\\n`` (is multiline) or '#'. - - If ``write_empty_values`` is set, and the value is an empty string, it - won't be quoted. - """ - if multiline and self.write_empty_values and value == '': - # Only if multiline is set, so that it is used for values not - # keys, and not values that are part of a list - return '' - - if multiline and isinstance(value, (list, tuple)): - if not value: - return ',' - elif len(value) == 1: - return self._quote(value[0], multiline=False) + ',' - return ', '.join([self._quote(val, multiline=False) - for val in value]) - if not isinstance(value, six.string_types): - if self.stringify: - # intentially 'str' because it's just whatever the "normal" - # string type is for the python version we're dealing with - value = str(value) - else: - raise TypeError('Value "%s" is not a string.' % value) - - if not value: - return '""' - - no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value - need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value )) - hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value) - check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote - - if check_for_single: - if not self.list_values: - # we don't quote if ``list_values=False`` - quot = noquot - # for normal values either single or double quotes will do - elif '\n' in value: - # will only happen if multiline is off - e.g. '\n' in key - raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) - elif ((value[0] not in wspace_plus) and - (value[-1] not in wspace_plus) and - (',' not in value)): - quot = noquot - else: - quot = self._get_single_quote(value) - else: - # if value has '\n' or "'" *and* '"', it will need triple quotes - quot = _get_triple_quote(value) - - if quot == noquot and '#' in value and self.list_values: - quot = self._get_single_quote(value) - - return quot % value - - - def _get_single_quote(self, value): - if ("'" in value) and ('"' in value): - raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) - elif '"' in value: - quot = squot - else: - quot = dquot - return quot - - - def _handle_value(self, value): - """ - Given a value string, unquote, remove comment, - handle lists. (including empty and single member lists) - """ - if self._inspec: - # Parsing a configspec so don't handle comments - return (value, '') - # do we look for lists in values ? - if not self.list_values: - mat = self._nolistvalue.match(value) - if mat is None: - raise SyntaxError() - # NOTE: we don't unquote here - return mat.groups() - # - mat = self._valueexp.match(value) - if mat is None: - # the value is badly constructed, probably badly quoted, - # or an invalid list - raise SyntaxError() - (list_values, single, empty_list, comment) = mat.groups() - if (list_values == '') and (single is None): - # change this if you want to accept empty values - raise SyntaxError() - # NOTE: note there is no error handling from here if the regex - # is wrong: then incorrect values will slip through - if empty_list is not None: - # the single comma - meaning an empty list - return ([], comment) - if single is not None: - # handle empty values - if list_values and not single: - # FIXME: the '' is a workaround because our regex now matches - # '' at the end of a list if it has a trailing comma - single = None - else: - single = single or '""' - single = self._unquote(single) - if list_values == '': - # not a list value - return (single, comment) - the_list = self._listvalueexp.findall(list_values) - the_list = [self._unquote(val) for val in the_list] - if single is not None: - the_list += [single] - return (the_list, comment) - - - def _multiline(self, value, infile, cur_index, maxline): - """Extract the value, where we are in a multiline situation.""" - quot = value[:3] - newvalue = value[3:] - single_line = self._triple_quote[quot][0] - multi_line = self._triple_quote[quot][1] - mat = single_line.match(value) - if mat is not None: - retval = list(mat.groups()) - retval.append(cur_index) - return retval - elif newvalue.find(quot) != -1: - # somehow the triple quote is missing - raise SyntaxError() - # - while cur_index < maxline: - cur_index += 1 - newvalue += '\n' - line = infile[cur_index] - if line.find(quot) == -1: - newvalue += line - else: - # end of multiline, process it - break - else: - # we've got to the end of the config, oops... - raise SyntaxError() - mat = multi_line.match(line) - if mat is None: - # a badly formed line - raise SyntaxError() - (value, comment) = mat.groups() - return (newvalue + value, comment, cur_index) - - - def _handle_configspec(self, configspec): - """Parse the configspec.""" - # FIXME: Should we check that the configspec was created with the - # correct settings ? (i.e. ``list_values=False``) - if not isinstance(configspec, ConfigObj): - try: - configspec = ConfigObj(configspec, - raise_errors=True, - file_error=True, - _inspec=True) - except ConfigObjError as cause: - # FIXME: Should these errors have a reference - # to the already parsed ConfigObj ? - raise ConfigspecError('Parsing configspec failed: %s' % cause) - except IOError as cause: - raise IOError('Reading configspec failed: %s' % cause) - - self.configspec = configspec - - - - def _set_configspec(self, section, copy): - """ - Called by validate. Handles setting the configspec on subsections - including sections to be validated by __many__ - """ - configspec = section.configspec - many = configspec.get('__many__') - if isinstance(many, dict): - for entry in section.sections: - if entry not in configspec: - section[entry].configspec = many - - for entry in configspec.sections: - if entry == '__many__': - continue - if entry not in section: - section[entry] = {} - section[entry]._created = True - if copy: - # copy comments - section.comments[entry] = configspec.comments.get(entry, []) - section.inline_comments[entry] = configspec.inline_comments.get(entry, '') - - # Could be a scalar when we expect a section - if isinstance(section[entry], Section): - section[entry].configspec = configspec[entry] - - - def _write_line(self, indent_string, entry, this_entry, comment): - """Write an individual line, for the write method""" - # NOTE: the calls to self._quote here handles non-StringType values. - if not self.unrepr: - val = self._decode_element(self._quote(this_entry)) - else: - val = repr(this_entry) - return '%s%s%s%s%s' % (indent_string, - self._decode_element(self._quote(entry, multiline=False)), - ' = ', - val, - self._decode_element(comment)) - - - def _write_marker(self, indent_string, depth, entry, comment): - """Write a section marker line""" - entry_str = self._decode_element(entry) - title = self._quote(entry_str, multiline=False) - if entry_str and title[0] in '\'"' and title[1:-1] == entry_str: - # titles are in '[]' already, so quoting for contained quotes is not necessary (#74) - title = entry_str - return '%s%s%s%s%s' % (indent_string, - '[' * depth, - title, - ']' * depth, - self._decode_element(comment)) - - - def _handle_comment(self, comment): - """Deal with a comment.""" - if not comment.strip(): - return comment or '' # return trailing whitespace as-is - start = self.indent_type - if not comment.lstrip().startswith('#'): - start += ' # ' - return (start + comment) - - - # Public methods - - def write(self, outfile=None, section=None): - """ - Write the current ConfigObj as a file - - tekNico: FIXME: use StringIO instead of real files - - >>> filename = a.filename - >>> a.filename = 'test.ini' - >>> a.write() - >>> a.filename = filename - >>> a == ConfigObj('test.ini', raise_errors=True) - 1 - >>> import os - >>> os.remove('test.ini') - """ - if self.indent_type is None: - # this can be true if initialised from a dictionary - self.indent_type = DEFAULT_INDENT_TYPE - - out = [] - comment_markers = tuple(self.COMMENT_MARKERS) - comment_marker_default = comment_markers[0] + ' ' - if section is None: - int_val = self.interpolation - self.interpolation = False - section = self - for line in self.initial_comment: - line = self._decode_element(line) - stripped_line = line.strip() - if stripped_line and not stripped_line.startswith(comment_markers): - line = comment_marker_default + line - out.append(line) - - indent_string = self.indent_type * section.depth - for entry in (section.scalars + section.sections): - if entry in section.defaults: - # don't write out default values - continue - for comment_line in section.comments[entry]: - comment_line = self._decode_element(comment_line.lstrip()) - if comment_line and not comment_line.startswith(comment_markers): - comment_line = comment_marker_default + comment_line - out.append(indent_string + comment_line) - this_entry = section[entry] - comment = self._handle_comment(section.inline_comments[entry]) - - if isinstance(this_entry, Section): - # a section - out.append(self._write_marker( - indent_string, - this_entry.depth, - entry, - comment)) - out.extend(self.write(section=this_entry)) - else: - out.append(self._write_line( - indent_string, - entry, - this_entry, - comment)) - - if section is self: - for line in self.final_comment: - line = self._decode_element(line) - stripped_line = line.strip() - if stripped_line and not stripped_line.startswith(comment_markers): - line = comment_marker_default + line - out.append(line) - self.interpolation = int_val - - if section is not self: - return out - - if (self.filename is None) and (outfile is None): - # output a list of lines - # might need to encode - # NOTE: This will *screw* UTF16, each line will start with the BOM - if self.encoding: - out = [l.encode(self.encoding) for l in out] - if (self.BOM and ((self.encoding is None) or - (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): - # Add the UTF8 BOM - if not out: - out.append('') - out[0] = BOM_UTF8 + out[0] - return out - - # Turn the list to a string, joined with correct newlines - newline = self.newlines or os.linesep - if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w' - and sys.platform == 'win32' and newline == '\r\n'): - # Windows specific hack to avoid writing '\r\r\n' - newline = '\n' - output = newline.join(out) - if not output.endswith(newline): - output += newline - - if isinstance(output, six.binary_type): - output_bytes = output - else: - output_bytes = output.encode(self.encoding or - self.default_encoding or - 'ascii') - - if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)): - # Add the UTF8 BOM - output_bytes = BOM_UTF8 + output_bytes - - if outfile is not None: - outfile.write(output_bytes) - else: - with open(self.filename, 'wb') as h: - h.write(output_bytes) - - def validate(self, validator, preserve_errors=False, copy=False, - section=None): - """ - Test the ConfigObj against a configspec. - - It uses the ``validator`` object from *validate.py*. - - To run ``validate`` on the current ConfigObj, call: :: - - test = config.validate(validator) - - (Normally having previously passed in the configspec when the ConfigObj - was created - you can dynamically assign a dictionary of checks to the - ``configspec`` attribute of a section though). - - It returns ``True`` if everything passes, or a dictionary of - pass/fails (True/False). If every member of a subsection passes, it - will just have the value ``True``. (It also returns ``False`` if all - members fail). - - In addition, it converts the values from strings to their native - types if their checks pass (and ``stringify`` is set). - - If ``preserve_errors`` is ``True`` (``False`` is default) then instead - of a marking a fail with a ``False``, it will preserve the actual - exception object. This can contain info about the reason for failure. - For example the ``VdtValueTooSmallError`` indicates that the value - supplied was too small. If a value (or section) is missing it will - still be marked as ``False``. - - You must have the validate module to use ``preserve_errors=True``. - - You can then use the ``flatten_errors`` function to turn your nested - results dictionary into a flattened list of failures - useful for - displaying meaningful error messages. - """ - if section is None: - if self.configspec is None: - raise ValueError('No configspec supplied.') - if preserve_errors: - # We do this once to remove a top level dependency on the validate module - # Which makes importing configobj faster - from configobj.validate import VdtMissingValue - self._vdtMissingValue = VdtMissingValue - - section = self - - if copy: - section.initial_comment = section.configspec.initial_comment - section.final_comment = section.configspec.final_comment - section.encoding = section.configspec.encoding - section.BOM = section.configspec.BOM - section.newlines = section.configspec.newlines - section.indent_type = section.configspec.indent_type - - # - # section.default_values.clear() #?? - configspec = section.configspec - self._set_configspec(section, copy) - - - def validate_entry(entry, spec, val, missing, ret_true, ret_false): - section.default_values.pop(entry, None) - - try: - section.default_values[entry] = validator.get_default_value(configspec[entry]) - except (KeyError, AttributeError, validator.baseErrorClass): - # No default, bad default or validator has no 'get_default_value' - # (e.g. SimpleVal) - pass - - try: - check = validator.check(spec, - val, - missing=missing - ) - except validator.baseErrorClass as cause: - if not preserve_errors or isinstance(cause, self._vdtMissingValue): - out[entry] = False - else: - # preserve the error - out[entry] = cause - ret_false = False - ret_true = False - else: - ret_false = False - out[entry] = True - if self.stringify or missing: - # if we are doing type conversion - # or the value is a supplied default - if not self.stringify: - if isinstance(check, (list, tuple)): - # preserve lists - check = [self._str(item) for item in check] - elif missing and check is None: - # convert the None from a default to a '' - check = '' - else: - check = self._str(check) - if (check != val) or missing: - section[entry] = check - if not copy and missing and entry not in section.defaults: - section.defaults.append(entry) - return ret_true, ret_false - - # - out = {} - ret_true = True - ret_false = True - - unvalidated = [k for k in section.scalars if k not in configspec] - incorrect_sections = [k for k in configspec.sections if k in section.scalars] - incorrect_scalars = [k for k in configspec.scalars if k in section.sections] - - for entry in configspec.scalars: - if entry in ('__many__', '___many___'): - # reserved names - continue - if (not entry in section.scalars) or (entry in section.defaults): - # missing entries - # or entries from defaults - missing = True - val = None - if copy and entry not in section.scalars: - # copy comments - section.comments[entry] = ( - configspec.comments.get(entry, [])) - section.inline_comments[entry] = ( - configspec.inline_comments.get(entry, '')) - # - else: - missing = False - val = section[entry] - - ret_true, ret_false = validate_entry(entry, configspec[entry], val, - missing, ret_true, ret_false) - - many = None - if '__many__' in configspec.scalars: - many = configspec['__many__'] - elif '___many___' in configspec.scalars: - many = configspec['___many___'] - - if many is not None: - for entry in unvalidated: - val = section[entry] - ret_true, ret_false = validate_entry(entry, many, val, False, - ret_true, ret_false) - unvalidated = [] - - for entry in incorrect_scalars: - ret_true = False - if not preserve_errors: - out[entry] = False - else: - ret_false = False - msg = 'Value %r was provided as a section' % entry - out[entry] = validator.baseErrorClass(msg) - for entry in incorrect_sections: - ret_true = False - if not preserve_errors: - out[entry] = False - else: - ret_false = False - msg = 'Section %r was provided as a single value' % entry - out[entry] = validator.baseErrorClass(msg) - - # Missing sections will have been created as empty ones when the - # configspec was read. - for entry in section.sections: - # FIXME: this means DEFAULT is not copied in copy mode - if section is self and entry == 'DEFAULT': - continue - if section[entry].configspec is None: - unvalidated.append(entry) - continue - if copy: - section.comments[entry] = configspec.comments.get(entry, []) - section.inline_comments[entry] = configspec.inline_comments.get(entry, '') - check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) - out[entry] = check - if check == False: - ret_true = False - elif check == True: - ret_false = False - else: - ret_true = False - - section.extra_values = unvalidated - if preserve_errors and not section._created: - # If the section wasn't created (i.e. it wasn't missing) - # then we can't return False, we need to preserve errors - ret_false = False - # - if ret_false and preserve_errors and out: - # If we are preserving errors, but all - # the failures are from missing sections / values - # then we can return False. Otherwise there is a - # real failure that we need to preserve. - ret_false = not any(out.values()) - if ret_true: - return True - elif ret_false: - return False - return out - - - def reset(self): - """Clear ConfigObj instance and restore to 'freshly created' state.""" - self.clear() - self._initialise() - # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload) - # requires an empty dictionary - self.configspec = None - # Just to be sure ;-) - self._original_configspec = None - - - def reload(self): - """ - Reload a ConfigObj from file. - - This method raises a ``ReloadError`` if the ConfigObj doesn't have - a filename attribute pointing to a file. - """ - if not isinstance(self.filename, six.string_types): - raise ReloadError() - - filename = self.filename - current_options = {} - for entry in OPTION_DEFAULTS: - if entry == 'configspec': - continue - current_options[entry] = getattr(self, entry) - - configspec = self._original_configspec - current_options['configspec'] = configspec - - self.clear() - self._initialise(current_options) - self._load(filename, configspec) - - - -class SimpleVal(object): - """ - A simple validator. - Can be used to check that all members expected are present. - - To use it, provide a configspec with all your members in (the value given - will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` - method of your ``ConfigObj``. ``validate`` will return ``True`` if all - members are present, or a dictionary with True/False meaning - present/missing. (Whole missing sections will be replaced with ``False``) - """ - - def __init__(self): - self.baseErrorClass = ConfigObjError - - def check(self, check, member, missing=False): - """A dummy check method, always returns the value unchanged.""" - if missing: - raise self.baseErrorClass() - return member - - -def flatten_errors(cfg, res, levels=None, results=None): - """ - An example function that will turn a nested dictionary of results - (as returned by ``ConfigObj.validate``) into a flat list. - - ``cfg`` is the ConfigObj instance being checked, ``res`` is the results - dictionary returned by ``validate``. - - (This is a recursive function, so you shouldn't use the ``levels`` or - ``results`` arguments - they are used by the function.) - - Returns a list of keys that failed. Each member of the list is a tuple:: - - ([list of sections...], key, result) - - If ``validate`` was called with ``preserve_errors=False`` (the default) - then ``result`` will always be ``False``. - - *list of sections* is a flattened list of sections that the key was found - in. - - If the section was missing (or a section was expected and a scalar provided - - or vice-versa) then key will be ``None``. - - If the value (or section) was missing then ``result`` will be ``False``. - - If ``validate`` was called with ``preserve_errors=True`` and a value - was present, but failed the check, then ``result`` will be the exception - object returned. You can use this as a string that describes the failure. - - For example *The value "3" is of the wrong type*. - """ - if levels is None: - # first time called - levels = [] - results = [] - if res == True: - return sorted(results) - if res == False or isinstance(res, Exception): - results.append((levels[:], None, res)) - if levels: - levels.pop() - return sorted(results) - for (key, val) in list(res.items()): - if val == True: - continue - if isinstance(cfg.get(key), Mapping): - # Go down one level - levels.append(key) - flatten_errors(cfg[key], val, levels, results) - continue - results.append((levels[:], key, val)) - # - # Go up one level - if levels: - levels.pop() - # - return sorted(results) - - -def get_extra_values(conf, _prepend=()): - """ - Find all the values and sections not in the configspec from a validated - ConfigObj. - - ``get_extra_values`` returns a list of tuples where each tuple represents - either an extra section, or an extra value. - - The tuples contain two values, a tuple representing the section the value - is in and the name of the extra values. For extra values in the top level - section the first member will be an empty tuple. For values in the 'foo' - section the first member will be ``('foo',)``. For members in the 'bar' - subsection of the 'foo' section the first member will be ``('foo', 'bar')``. - - NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't - been validated it will return an empty list. - """ - out = [] - - out.extend([(_prepend, name) for name in conf.extra_values]) - for name in conf.sections: - if name not in conf.extra_values: - out.extend(get_extra_values(conf[name], _prepend + (name,))) - return out - - -"""*A programming language is a medium of expression.* - Paul Graham""" diff --git a/libs/common/configobj/_version.py b/libs/common/configobj/_version.py deleted file mode 100644 index 19a71401..00000000 --- a/libs/common/configobj/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Project version""" -__version__ = '5.1.0' diff --git a/libs/common/configobj/validate.py b/libs/common/configobj/validate.py deleted file mode 100644 index 44759d83..00000000 --- a/libs/common/configobj/validate.py +++ /dev/null @@ -1,1474 +0,0 @@ -# validate.py -# -*- coding: utf-8 -*- -# pylint: disable= -# -# A Validator object. -# -# Copyright (C) 2005-2014: -# (name) : (email) -# Michael Foord: fuzzyman AT voidspace DOT org DOT uk -# Mark Andrews: mark AT la-la DOT com -# Nicola Larosa: nico AT tekNico DOT net -# Rob Dennis: rdennis AT gmail DOT com -# Eli Courtwright: eli AT courtwright DOT org - -# This software is licensed under the terms of the BSD license. -# http://opensource.org/licenses/BSD-3-Clause - -# ConfigObj 5 - main repository for documentation and issue tracking: -# https://github.com/DiffSK/configobj - -""" - The Validator object is used to check that supplied values - conform to a specification. - - The value can be supplied as a string - e.g. from a config file. - In this case the check will also *convert* the value to - the required type. This allows you to add validation - as a transparent layer to access data stored as strings. - The validation checks that the data is correct *and* - converts it to the expected type. - - Some standard checks are provided for basic data types. - Additional checks are easy to write. They can be - provided when the ``Validator`` is instantiated or - added afterwards. - - The standard functions work with the following basic data types : - - * integers - * floats - * booleans - * strings - * ip_addr - - plus lists of these datatypes - - Adding additional checks is done through coding simple functions. - - The full set of standard checks are : - - * 'integer': matches integer values (including negative) - Takes optional 'min' and 'max' arguments : :: - - integer() - integer(3, 9) # any value from 3 to 9 - integer(min=0) # any positive value - integer(max=9) - - * 'float': matches float values - Has the same parameters as the integer check. - - * 'boolean': matches boolean values - ``True`` or ``False`` - Acceptable string values for True are : - true, on, yes, 1 - Acceptable string values for False are : - false, off, no, 0 - - Any other value raises an error. - - * 'ip_addr': matches an Internet Protocol address, v.4, represented - by a dotted-quad string, i.e. '1.2.3.4'. - - * 'string': matches any string. - Takes optional keyword args 'min' and 'max' - to specify min and max lengths of the string. - - * 'list': matches any list. - Takes optional keyword args 'min', and 'max' to specify min and - max sizes of the list. (Always returns a list.) - - * 'tuple': matches any tuple. - Takes optional keyword args 'min', and 'max' to specify min and - max sizes of the tuple. (Always returns a tuple.) - - * 'int_list': Matches a list of integers. - Takes the same arguments as list. - - * 'float_list': Matches a list of floats. - Takes the same arguments as list. - - * 'bool_list': Matches a list of boolean values. - Takes the same arguments as list. - - * 'ip_addr_list': Matches a list of IP addresses. - Takes the same arguments as list. - - * 'string_list': Matches a list of strings. - Takes the same arguments as list. - - * 'mixed_list': Matches a list with different types in - specific positions. List size must match - the number of arguments. - - Each position can be one of : - 'integer', 'float', 'ip_addr', 'string', 'boolean' - - So to specify a list with two strings followed - by two integers, you write the check as : :: - - mixed_list('string', 'string', 'integer', 'integer') - - * 'pass': This check matches everything ! It never fails - and the value is unchanged. - - It is also the default if no check is specified. - - * 'option': This check matches any from a list of options. - You specify this check with : :: - - option('option 1', 'option 2', 'option 3') - - You can supply a default value (returned if no value is supplied) - using the default keyword argument. - - You specify a list argument for default using a list constructor syntax in - the check : :: - - checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3')) - - A badly formatted set of arguments will raise a ``VdtParamError``. -""" -import re -import sys -from pprint import pprint - -__version__ = '1.0.1' - -__all__ = ( - 'dottedQuadToNum', - 'numToDottedQuad', - 'ValidateError', - 'VdtUnknownCheckError', - 'VdtParamError', - 'VdtTypeError', - 'VdtValueError', - 'VdtValueTooSmallError', - 'VdtValueTooBigError', - 'VdtValueTooShortError', - 'VdtValueTooLongError', - 'VdtMissingValue', - 'Validator', - 'is_integer', - 'is_float', - 'is_boolean', - 'is_list', - 'is_tuple', - 'is_ip_addr', - 'is_string', - 'is_int_list', - 'is_bool_list', - 'is_float_list', - 'is_string_list', - 'is_ip_addr_list', - 'is_mixed_list', - 'is_option', - # '__docformat__', -- where is this supposed to come from? [jhe 2015-04-11] -) - - -#TODO - #21 - six is part of the repo now, but we didn't switch over to it here -# this could be replaced if six is used for compatibility, or there are no -# more assertions about items being a string -if sys.version_info < (3,): - string_type = basestring -else: - string_type = str - # so tests that care about unicode on 2.x can specify unicode, and the same - # tests when run on 3.x won't complain about a undefined name "unicode" - # since all strings are unicode on 3.x we just want to pass it through - # unchanged - unicode = lambda x: x - # in python 3, all ints are equivalent to python 2 longs, and they'll - # never show "L" in the repr - long = int - -_list_arg = re.compile(r''' - (?: - ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\( - ( - (?: - \s* - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s\)][^,\)]*?) # unquoted - ) - \s*,\s* - )* - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s\)][^,\)]*?) # unquoted - )? # last one - ) - \) - ) -''', re.VERBOSE | re.DOTALL) # two groups - -_list_members = re.compile(r''' - ( - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s=][^,=]*?) # unquoted - ) - (?: - (?:\s*,\s*)|(?:\s*$) # comma - ) -''', re.VERBOSE | re.DOTALL) # one group - -_paramstring = r''' - (?: - ( - (?: - [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\( - (?: - \s* - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s\)][^,\)]*?) # unquoted - ) - \s*,\s* - )* - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s\)][^,\)]*?) # unquoted - )? # last one - \) - )| - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s=][^,=]*?)| # unquoted - (?: # keyword argument - [a-zA-Z_][a-zA-Z0-9_]*\s*=\s* - (?: - (?:".*?")| # double quotes - (?:'.*?')| # single quotes - (?:[^'",\s=][^,=]*?) # unquoted - ) - ) - ) - ) - (?: - (?:\s*,\s*)|(?:\s*$) # comma - ) - ) - ''' - -_matchstring = '^%s*' % _paramstring - - -def dottedQuadToNum(ip): - """ - Convert decimal dotted quad string to long integer - - >>> int(dottedQuadToNum('1 ')) - 1 - >>> int(dottedQuadToNum('1.2.3.4')) - 16909060 - """ - - # import here to avoid it when ip_addr values are not used - import socket, struct - - try: - return struct.unpack('!L', - socket.inet_aton(ip.strip()))[0] - except socket.error: - raise ValueError('Not a good dotted-quad IP: %s' % ip) - return - - -def numToDottedQuad(num): - """ - Convert int or long int to dotted quad string - - >>> numToDottedQuad(long(-1)) - Traceback (most recent call last): - ValueError: Not a good numeric IP: -1 - >>> numToDottedQuad(long(1)) - '0.0.0.1' - >>> numToDottedQuad(long(16777218)) - '1.0.0.2' - >>> numToDottedQuad(long(16908291)) - '1.2.0.3' - >>> numToDottedQuad(long(16909060)) - '1.2.3.4' - >>> numToDottedQuad(long(4294967295)) - '255.255.255.255' - >>> numToDottedQuad(long(4294967296)) - Traceback (most recent call last): - ValueError: Not a good numeric IP: 4294967296 - >>> numToDottedQuad(-1) - Traceback (most recent call last): - ValueError: Not a good numeric IP: -1 - >>> numToDottedQuad(1) - '0.0.0.1' - >>> numToDottedQuad(16777218) - '1.0.0.2' - >>> numToDottedQuad(16908291) - '1.2.0.3' - >>> numToDottedQuad(16909060) - '1.2.3.4' - >>> numToDottedQuad(4294967295) - '255.255.255.255' - >>> numToDottedQuad(4294967296) - Traceback (most recent call last): - ValueError: Not a good numeric IP: 4294967296 - - """ - - # import here to avoid it when ip_addr values are not used - import socket, struct - - # no need to intercept here, 4294967295L is fine - if num > long(4294967295) or num < 0: - raise ValueError('Not a good numeric IP: %s' % num) - try: - return socket.inet_ntoa( - struct.pack('!L', long(num))) - except (socket.error, struct.error, OverflowError): - raise ValueError('Not a good numeric IP: %s' % num) - - -class ValidateError(Exception): - """ - This error indicates that the check failed. - It can be the base class for more specific errors. - - Any check function that fails ought to raise this error. - (or a subclass) - - >>> raise ValidateError - Traceback (most recent call last): - ValidateError - """ - - -class VdtMissingValue(ValidateError): - """No value was supplied to a check that needed one.""" - - -class VdtUnknownCheckError(ValidateError): - """An unknown check function was requested""" - - def __init__(self, value): - """ - >>> raise VdtUnknownCheckError('yoda') - Traceback (most recent call last): - VdtUnknownCheckError: the check "yoda" is unknown. - """ - ValidateError.__init__(self, 'the check "{}" is unknown.'.format(value)) - - -class VdtParamError(SyntaxError): - """An incorrect parameter was passed""" - - NOT_GIVEN = object() - - def __init__(self, name_or_msg, value=NOT_GIVEN): - """ - >>> raise VdtParamError('yoda', 'jedi') - Traceback (most recent call last): - VdtParamError: passed an incorrect value "jedi" for parameter "yoda". - >>> raise VdtParamError('preformatted message') - Traceback (most recent call last): - VdtParamError: preformatted message - """ - if value is self.NOT_GIVEN: - SyntaxError.__init__(self, name_or_msg) - else: - SyntaxError.__init__(self, 'passed an incorrect value "{}" for parameter "{}".'.format(value, name_or_msg)) - - -class VdtTypeError(ValidateError): - """The value supplied was of the wrong type""" - - def __init__(self, value): - """ - >>> raise VdtTypeError('jedi') - Traceback (most recent call last): - VdtTypeError: the value "jedi" is of the wrong type. - """ - ValidateError.__init__(self, 'the value "{}" is of the wrong type.'.format(value)) - - -class VdtValueError(ValidateError): - """The value supplied was of the correct type, but was not an allowed value.""" - - def __init__(self, value): - """ - >>> raise VdtValueError('jedi') - Traceback (most recent call last): - VdtValueError: the value "jedi" is unacceptable. - """ - ValidateError.__init__(self, 'the value "{}" is unacceptable.'.format(value)) - - -class VdtValueTooSmallError(VdtValueError): - """The value supplied was of the correct type, but was too small.""" - - def __init__(self, value): - """ - >>> raise VdtValueTooSmallError('0') - Traceback (most recent call last): - VdtValueTooSmallError: the value "0" is too small. - """ - ValidateError.__init__(self, 'the value "{}" is too small.'.format(value)) - - -class VdtValueTooBigError(VdtValueError): - """The value supplied was of the correct type, but was too big.""" - - def __init__(self, value): - """ - >>> raise VdtValueTooBigError('1') - Traceback (most recent call last): - VdtValueTooBigError: the value "1" is too big. - """ - ValidateError.__init__(self, 'the value "{}" is too big.'.format(value)) - - -class VdtValueTooShortError(VdtValueError): - """The value supplied was of the correct type, but was too short.""" - - def __init__(self, value): - """ - >>> raise VdtValueTooShortError('jed') - Traceback (most recent call last): - VdtValueTooShortError: the value "jed" is too short. - """ - ValidateError.__init__( - self, - 'the value "{}" is too short.'.format(value)) - - -class VdtValueTooLongError(VdtValueError): - """The value supplied was of the correct type, but was too long.""" - - def __init__(self, value): - """ - >>> raise VdtValueTooLongError('jedie') - Traceback (most recent call last): - VdtValueTooLongError: the value "jedie" is too long. - """ - ValidateError.__init__(self, 'the value "{}" is too long.'.format(value)) - - -class Validator(object): - """ - Validator is an object that allows you to register a set of 'checks'. - These checks take input and test that it conforms to the check. - - This can also involve converting the value from a string into - the correct datatype. - - The ``check`` method takes an input string which configures which - check is to be used and applies that check to a supplied value. - - An example input string would be: - 'int_range(param1, param2)' - - You would then provide something like: - - >>> def int_range_check(value, min, max): - ... # turn min and max from strings to integers - ... min = int(min) - ... max = int(max) - ... # check that value is of the correct type. - ... # possible valid inputs are integers or strings - ... # that represent integers - ... if not isinstance(value, (int, long, string_type)): - ... raise VdtTypeError(value) - ... elif isinstance(value, string_type): - ... # if we are given a string - ... # attempt to convert to an integer - ... try: - ... value = int(value) - ... except ValueError: - ... raise VdtValueError(value) - ... # check the value is between our constraints - ... if not min <= value: - ... raise VdtValueTooSmallError(value) - ... if not value <= max: - ... raise VdtValueTooBigError(value) - ... return value - - >>> fdict = {'int_range': int_range_check} - >>> vtr1 = Validator(fdict) - >>> vtr1.check('int_range(20, 40)', '30') - 30 - >>> vtr1.check('int_range(20, 40)', '60') - Traceback (most recent call last): - VdtValueTooBigError: the value "60" is too big. - - New functions can be added with : :: - - >>> vtr2 = Validator() - >>> vtr2.functions['int_range'] = int_range_check - - Or by passing in a dictionary of functions when Validator - is instantiated. - - Your functions *can* use keyword arguments, - but the first argument should always be 'value'. - - If the function doesn't take additional arguments, - the parentheses are optional in the check. - It can be written with either of : :: - - keyword = function_name - keyword = function_name() - - The first program to utilise Validator() was Michael Foord's - ConfigObj, an alternative to ConfigParser which supports lists and - can validate a config file using a config schema. - For more details on using Validator with ConfigObj see: - https://configobj.readthedocs.io/en/latest/configobj.html - """ - - # this regex does the initial parsing of the checks - _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL) - - # this regex takes apart keyword arguments - _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) - - - # this regex finds keyword=list(....) type values - _list_arg = _list_arg - - # this regex takes individual values out of lists - in one pass - _list_members = _list_members - - # These regexes check a set of arguments for validity - # and then pull the members out - _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) - _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) - - - def __init__(self, functions=None): - """ - >>> vtri = Validator() - """ - self.functions = { - '': self._pass, - 'integer': is_integer, - 'float': is_float, - 'boolean': is_boolean, - 'ip_addr': is_ip_addr, - 'string': is_string, - 'list': is_list, - 'tuple': is_tuple, - 'int_list': is_int_list, - 'float_list': is_float_list, - 'bool_list': is_bool_list, - 'ip_addr_list': is_ip_addr_list, - 'string_list': is_string_list, - 'mixed_list': is_mixed_list, - 'pass': self._pass, - 'option': is_option, - 'force_list': force_list, - } - if functions is not None: - self.functions.update(functions) - # tekNico: for use by ConfigObj - self.baseErrorClass = ValidateError - self._cache = {} - - - def check(self, check, value, missing=False): - """ - Usage: check(check, value) - - Arguments: - check: string representing check to apply (including arguments) - value: object to be checked - - Returns value, converted to correct type if necessary - - If the check fails, raises a ``ValidateError`` subclass. - - >>> vtor.check('yoda', '') - Traceback (most recent call last): - VdtUnknownCheckError: the check "yoda" is unknown. - >>> vtor.check('yoda()', '') - Traceback (most recent call last): - VdtUnknownCheckError: the check "yoda" is unknown. - - >>> vtor.check('string(default="")', '', missing=True) - '' - """ - fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) - - if missing: - if default is None: - # no information needed here - to be handled by caller - raise VdtMissingValue() - value = self._handle_none(default) - - if value is None: - return None - - return self._check_value(value, fun_name, fun_args, fun_kwargs) - - - def _handle_none(self, value): - if value == 'None': - return None - elif value in ("'None'", '"None"'): - # Special case a quoted None - value = self._unquote(value) - return value - - - def _parse_with_caching(self, check): - if check in self._cache: - fun_name, fun_args, fun_kwargs, default = self._cache[check] - # We call list and dict below to work with *copies* of the data - # rather than the original (which are mutable of course) - fun_args = list(fun_args) - fun_kwargs = dict(fun_kwargs) - else: - fun_name, fun_args, fun_kwargs, default = self._parse_check(check) - fun_kwargs = {str(key): value for (key, value) in list(fun_kwargs.items())} - self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default - return fun_name, fun_args, fun_kwargs, default - - - def _check_value(self, value, fun_name, fun_args, fun_kwargs): - try: - fun = self.functions[fun_name] - except KeyError: - raise VdtUnknownCheckError(fun_name) - else: - return fun(value, *fun_args, **fun_kwargs) - - - def _parse_check(self, check): - fun_match = self._func_re.match(check) - if fun_match: - fun_name = fun_match.group(1) - arg_string = fun_match.group(2) - arg_match = self._matchfinder.match(arg_string) - if arg_match is None: - # Bad syntax - raise VdtParamError('Bad syntax in check "%s".' % check) - fun_args = [] - fun_kwargs = {} - # pull out args of group 2 - for arg in self._paramfinder.findall(arg_string): - # args may need whitespace removing (before removing quotes) - arg = arg.strip() - listmatch = self._list_arg.match(arg) - if listmatch: - key, val = self._list_handle(listmatch) - fun_kwargs[key] = val - continue - keymatch = self._key_arg.match(arg) - if keymatch: - val = keymatch.group(2) - if not val in ("'None'", '"None"'): - # Special case a quoted None - val = self._unquote(val) - fun_kwargs[keymatch.group(1)] = val - continue - - fun_args.append(self._unquote(arg)) - else: - # allows for function names without (args) - return check, (), {}, None - - # Default must be deleted if the value is specified too, - # otherwise the check function will get a spurious "default" keyword arg - default = fun_kwargs.pop('default', None) - return fun_name, fun_args, fun_kwargs, default - - - def _unquote(self, val): - """Unquote a value if necessary.""" - if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]): - val = val[1:-1] - return val - - - def _list_handle(self, listmatch): - """Take apart a ``keyword=list('val, 'val')`` type string.""" - out = [] - name = listmatch.group(1) - args = listmatch.group(2) - for arg in self._list_members.findall(args): - out.append(self._unquote(arg)) - return name, out - - - def _pass(self, value): - """ - Dummy check that always passes - - >>> vtor.check('', 0) - 0 - >>> vtor.check('', '0') - '0' - """ - return value - - - def get_default_value(self, check): - """ - Given a check, return the default value for the check - (converted to the right type). - - If the check doesn't specify a default value then a - ``KeyError`` will be raised. - """ - fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) - if default is None: - raise KeyError('Check "%s" has no default value.' % check) - value = self._handle_none(default) - if value is None: - return value - return self._check_value(value, fun_name, fun_args, fun_kwargs) - - -def _is_num_param(names, values, to_float=False): - """ - Return numbers from inputs or raise VdtParamError. - - Lets ``None`` pass through. - Pass in keyword argument ``to_float=True`` to - use float for the conversion rather than int. - - >>> _is_num_param(('', ''), (0, 1.0)) - [0, 1] - >>> _is_num_param(('', ''), (0, 1.0), to_float=True) - [0.0, 1.0] - >>> _is_num_param(('a'), ('a')) - Traceback (most recent call last): - VdtParamError: passed an incorrect value "a" for parameter "a". - """ - fun = to_float and float or int - out_params = [] - for (name, val) in zip(names, values): - if val is None: - out_params.append(val) - elif isinstance(val, (int, long, float, string_type)): - try: - out_params.append(fun(val)) - except ValueError: - raise VdtParamError(name, val) - else: - raise VdtParamError(name, val) - return out_params - - -# built in checks -# you can override these by setting the appropriate name -# in Validator.functions -# note: if the params are specified wrongly in your input string, -# you will also raise errors. - -def is_integer(value, min=None, max=None): - """ - A check that tests that a given value is an integer (int, or long) - and optionally, between bounds. A negative value is accepted, while - a float will fail. - - If the value is a string, then the conversion is done - if possible. - Otherwise a VdtError is raised. - - >>> vtor.check('integer', '-1') - -1 - >>> vtor.check('integer', '0') - 0 - >>> vtor.check('integer', 9) - 9 - >>> vtor.check('integer', 'a') - Traceback (most recent call last): - VdtTypeError: the value "a" is of the wrong type. - >>> vtor.check('integer', '2.2') - Traceback (most recent call last): - VdtTypeError: the value "2.2" is of the wrong type. - >>> vtor.check('integer(10)', '20') - 20 - >>> vtor.check('integer(max=20)', '15') - 15 - >>> vtor.check('integer(10)', '9') - Traceback (most recent call last): - VdtValueTooSmallError: the value "9" is too small. - >>> vtor.check('integer(10)', 9) - Traceback (most recent call last): - VdtValueTooSmallError: the value "9" is too small. - >>> vtor.check('integer(max=20)', '35') - Traceback (most recent call last): - VdtValueTooBigError: the value "35" is too big. - >>> vtor.check('integer(max=20)', 35) - Traceback (most recent call last): - VdtValueTooBigError: the value "35" is too big. - >>> vtor.check('integer(0, 9)', False) - 0 - """ - (min_val, max_val) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking - ('min', 'max'), (min, max)) - if not isinstance(value, (int, long, string_type)): - raise VdtTypeError(value) - if isinstance(value, string_type): - # if it's a string - does it represent an integer ? - try: - value = int(value) - except ValueError: - raise VdtTypeError(value) - if (min_val is not None) and (value < min_val): - raise VdtValueTooSmallError(value) - if (max_val is not None) and (value > max_val): - raise VdtValueTooBigError(value) - return value - - -def is_float(value, min=None, max=None): - """ - A check that tests that a given value is a float - (an integer will be accepted), and optionally - that it is between bounds. - - If the value is a string, then the conversion is done - if possible. - Otherwise a VdtError is raised. - - This can accept negative values. - - >>> vtor.check('float', '2') - 2.0 - - From now on we multiply the value to avoid comparing decimals - - >>> vtor.check('float', '-6.8') * 10 - -68.0 - >>> vtor.check('float', '12.2') * 10 - 122.0 - >>> vtor.check('float', 8.4) * 10 - 84.0 - >>> vtor.check('float', 'a') - Traceback (most recent call last): - VdtTypeError: the value "a" is of the wrong type. - >>> vtor.check('float(10.1)', '10.2') * 10 - 102.0 - >>> vtor.check('float(max=20.2)', '15.1') * 10 - 151.0 - >>> vtor.check('float(10.0)', '9.0') - Traceback (most recent call last): - VdtValueTooSmallError: the value "9.0" is too small. - >>> vtor.check('float(max=20.0)', '35.0') - Traceback (most recent call last): - VdtValueTooBigError: the value "35.0" is too big. - """ - (min_val, max_val) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking - ('min', 'max'), (min, max), to_float=True) - if not isinstance(value, (int, long, float, string_type)): - raise VdtTypeError(value) - if not isinstance(value, float): - # if it's a string - does it represent a float ? - try: - value = float(value) - except ValueError: - raise VdtTypeError(value) - if (min_val is not None) and (value < min_val): - raise VdtValueTooSmallError(value) - if (max_val is not None) and (value > max_val): - raise VdtValueTooBigError(value) - return value - - -bool_dict = { - True: True, 'on': True, '1': True, 'true': True, 'yes': True, - False: False, 'off': False, '0': False, 'false': False, 'no': False, -} - - -def is_boolean(value): - """ - Check if the value represents a boolean. - - >>> vtor.check('boolean', 0) - 0 - >>> vtor.check('boolean', False) - 0 - >>> vtor.check('boolean', '0') - 0 - >>> vtor.check('boolean', 'off') - 0 - >>> vtor.check('boolean', 'false') - 0 - >>> vtor.check('boolean', 'no') - 0 - >>> vtor.check('boolean', 'nO') - 0 - >>> vtor.check('boolean', 'NO') - 0 - >>> vtor.check('boolean', 1) - 1 - >>> vtor.check('boolean', True) - 1 - >>> vtor.check('boolean', '1') - 1 - >>> vtor.check('boolean', 'on') - 1 - >>> vtor.check('boolean', 'true') - 1 - >>> vtor.check('boolean', 'yes') - 1 - >>> vtor.check('boolean', 'Yes') - 1 - >>> vtor.check('boolean', 'YES') - 1 - >>> vtor.check('boolean', '') - Traceback (most recent call last): - VdtTypeError: the value "" is of the wrong type. - >>> vtor.check('boolean', 'up') - Traceback (most recent call last): - VdtTypeError: the value "up" is of the wrong type. - - """ - if isinstance(value, string_type): - try: - return bool_dict[value.lower()] - except KeyError: - raise VdtTypeError(value) - # we do an equality test rather than an identity test - # this ensures Python 2.2 compatibility - # and allows 0 and 1 to represent True and False - if value == False: - return False - elif value == True: - return True - else: - raise VdtTypeError(value) - - -def is_ip_addr(value): - """ - Check that the supplied value is an Internet Protocol address, v.4, - represented by a dotted-quad string, i.e. '1.2.3.4'. - - >>> vtor.check('ip_addr', '1 ') - '1' - >>> vtor.check('ip_addr', ' 1.2') - '1.2' - >>> vtor.check('ip_addr', ' 1.2.3 ') - '1.2.3' - >>> vtor.check('ip_addr', '1.2.3.4') - '1.2.3.4' - >>> vtor.check('ip_addr', '0.0.0.0') - '0.0.0.0' - >>> vtor.check('ip_addr', '255.255.255.255') - '255.255.255.255' - >>> vtor.check('ip_addr', '255.255.255.256') - Traceback (most recent call last): - VdtValueError: the value "255.255.255.256" is unacceptable. - >>> vtor.check('ip_addr', '1.2.3.4.5') - Traceback (most recent call last): - VdtValueError: the value "1.2.3.4.5" is unacceptable. - >>> vtor.check('ip_addr', 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - """ - if not isinstance(value, string_type): - raise VdtTypeError(value) - value = value.strip() - try: - dottedQuadToNum(value) - except ValueError: - raise VdtValueError(value) - return value - - -def is_list(value, min=None, max=None): - """ - Check that the value is a list of values. - - You can optionally specify the minimum and maximum number of members. - - It does no check on list members. - - >>> vtor.check('list', ()) - [] - >>> vtor.check('list', []) - [] - >>> vtor.check('list', (1, 2)) - [1, 2] - >>> vtor.check('list', [1, 2]) - [1, 2] - >>> vtor.check('list(3)', (1, 2)) - Traceback (most recent call last): - VdtValueTooShortError: the value "(1, 2)" is too short. - >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6)) - Traceback (most recent call last): - VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. - >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4)) - [1, 2, 3, 4] - >>> vtor.check('list', 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - >>> vtor.check('list', '12') - Traceback (most recent call last): - VdtTypeError: the value "12" is of the wrong type. - """ - (min_len, max_len) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking - ('min', 'max'), (min, max)) - if isinstance(value, string_type): - raise VdtTypeError(value) - try: - num_members = len(value) - except TypeError: - raise VdtTypeError(value) - if min_len is not None and num_members < min_len: - raise VdtValueTooShortError(value) - if max_len is not None and num_members > max_len: - raise VdtValueTooLongError(value) - return list(value) - - -def is_tuple(value, min=None, max=None): - """ - Check that the value is a tuple of values. - - You can optionally specify the minimum and maximum number of members. - - It does no check on members. - - >>> vtor.check('tuple', ()) - () - >>> vtor.check('tuple', []) - () - >>> vtor.check('tuple', (1, 2)) - (1, 2) - >>> vtor.check('tuple', [1, 2]) - (1, 2) - >>> vtor.check('tuple(3)', (1, 2)) - Traceback (most recent call last): - VdtValueTooShortError: the value "(1, 2)" is too short. - >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6)) - Traceback (most recent call last): - VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. - >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4)) - (1, 2, 3, 4) - >>> vtor.check('tuple', 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - >>> vtor.check('tuple', '12') - Traceback (most recent call last): - VdtTypeError: the value "12" is of the wrong type. - """ - return tuple(is_list(value, min, max)) - - -def is_string(value, min=None, max=None): - """ - Check that the supplied value is a string. - - You can optionally specify the minimum and maximum number of members. - - >>> vtor.check('string', '0') - '0' - >>> vtor.check('string', 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - >>> vtor.check('string(2)', '12') - '12' - >>> vtor.check('string(2)', '1') - Traceback (most recent call last): - VdtValueTooShortError: the value "1" is too short. - >>> vtor.check('string(min=2, max=3)', '123') - '123' - >>> vtor.check('string(min=2, max=3)', '1234') - Traceback (most recent call last): - VdtValueTooLongError: the value "1234" is too long. - """ - if not isinstance(value, string_type): - raise VdtTypeError(value) - (min_len, max_len) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking - ('min', 'max'), (min, max)) - try: - num_members = len(value) - except TypeError: - raise VdtTypeError(value) - if min_len is not None and num_members < min_len: - raise VdtValueTooShortError(value) - if max_len is not None and num_members > max_len: - raise VdtValueTooLongError(value) - return value - - -def is_int_list(value, min=None, max=None): - """ - Check that the value is a list of integers. - - You can optionally specify the minimum and maximum number of members. - - Each list member is checked that it is an integer. - - >>> vtor.check('int_list', ()) - [] - >>> vtor.check('int_list', []) - [] - >>> vtor.check('int_list', (1, 2)) - [1, 2] - >>> vtor.check('int_list', [1, 2]) - [1, 2] - >>> vtor.check('int_list', [1, 'a']) - Traceback (most recent call last): - VdtTypeError: the value "a" is of the wrong type. - """ - return [is_integer(mem) for mem in is_list(value, min, max)] - - -def is_bool_list(value, min=None, max=None): - """ - Check that the value is a list of booleans. - - You can optionally specify the minimum and maximum number of members. - - Each list member is checked that it is a boolean. - - >>> vtor.check('bool_list', ()) - [] - >>> vtor.check('bool_list', []) - [] - >>> check_res = vtor.check('bool_list', (True, False)) - >>> check_res == [True, False] - 1 - >>> check_res = vtor.check('bool_list', [True, False]) - >>> check_res == [True, False] - 1 - >>> vtor.check('bool_list', [True, 'a']) - Traceback (most recent call last): - VdtTypeError: the value "a" is of the wrong type. - """ - return [is_boolean(mem) for mem in is_list(value, min, max)] - - -def is_float_list(value, min=None, max=None): - """ - Check that the value is a list of floats. - - You can optionally specify the minimum and maximum number of members. - - Each list member is checked that it is a float. - - >>> vtor.check('float_list', ()) - [] - >>> vtor.check('float_list', []) - [] - >>> vtor.check('float_list', (1, 2.0)) - [1.0, 2.0] - >>> vtor.check('float_list', [1, 2.0]) - [1.0, 2.0] - >>> vtor.check('float_list', [1, 'a']) - Traceback (most recent call last): - VdtTypeError: the value "a" is of the wrong type. - """ - return [is_float(mem) for mem in is_list(value, min, max)] - - -def is_string_list(value, min=None, max=None): - """ - Check that the value is a list of strings. - - You can optionally specify the minimum and maximum number of members. - - Each list member is checked that it is a string. - - >>> vtor.check('string_list', ()) - [] - >>> vtor.check('string_list', []) - [] - >>> vtor.check('string_list', ('a', 'b')) - ['a', 'b'] - >>> vtor.check('string_list', ['a', 1]) - Traceback (most recent call last): - VdtTypeError: the value "1" is of the wrong type. - >>> vtor.check('string_list', 'hello') - Traceback (most recent call last): - VdtTypeError: the value "hello" is of the wrong type. - """ - if isinstance(value, string_type): - raise VdtTypeError(value) - return [is_string(mem) for mem in is_list(value, min, max)] - - -def is_ip_addr_list(value, min=None, max=None): - """ - Check that the value is a list of IP addresses. - - You can optionally specify the minimum and maximum number of members. - - Each list member is checked that it is an IP address. - - >>> vtor.check('ip_addr_list', ()) - [] - >>> vtor.check('ip_addr_list', []) - [] - >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8')) - ['1.2.3.4', '5.6.7.8'] - >>> vtor.check('ip_addr_list', ['a']) - Traceback (most recent call last): - VdtValueError: the value "a" is unacceptable. - """ - return [is_ip_addr(mem) for mem in is_list(value, min, max)] - - -def force_list(value, min=None, max=None): - """ - Check that a value is a list, coercing strings into - a list with one member. Useful where users forget the - trailing comma that turns a single value into a list. - - You can optionally specify the minimum and maximum number of members. - A minimum of greater than one will fail if the user only supplies a - string. - - >>> vtor.check('force_list', ()) - [] - >>> vtor.check('force_list', []) - [] - >>> vtor.check('force_list', 'hello') - ['hello'] - """ - if not isinstance(value, (list, tuple)): - value = [value] - return is_list(value, min, max) - - - -fun_dict = { - int: is_integer, - 'int': is_integer, - 'integer': is_integer, - float: is_float, - 'float': is_float, - 'ip_addr': is_ip_addr, - str: is_string, - 'str': is_string, - 'string': is_string, - bool: is_boolean, - 'bool': is_boolean, - 'boolean': is_boolean, -} - - -def is_mixed_list(value, *args): - """ - Check that the value is a list. - Allow specifying the type of each member. - Work on lists of specific lengths. - - You specify each member as a positional argument specifying type - - Each type should be one of the following strings : - 'integer', 'float', 'ip_addr', 'string', 'boolean' - - So you can specify a list of two strings, followed by - two integers as : - - mixed_list('string', 'string', 'integer', 'integer') - - The length of the list must match the number of positional - arguments you supply. - - >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')" - >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True)) - >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] - 1 - >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True')) - >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] - 1 - >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True)) - Traceback (most recent call last): - VdtTypeError: the value "b" is of the wrong type. - >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a')) - Traceback (most recent call last): - VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short. - >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b')) - Traceback (most recent call last): - VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long. - >>> vtor.check(mix_str, 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - - >>> vtor.check('mixed_list("yoda")', ('a')) - Traceback (most recent call last): - VdtParamError: passed an incorrect value "KeyError('yoda',)" for parameter "'mixed_list'" - """ - try: - length = len(value) - except TypeError: - raise VdtTypeError(value) - if length < len(args): - raise VdtValueTooShortError(value) - elif length > len(args): - raise VdtValueTooLongError(value) - try: - return [fun_dict[arg](val) for arg, val in zip(args, value)] - except KeyError as cause: - raise VdtParamError('mixed_list', cause) - - -def is_option(value, *options): - """ - This check matches the value to any of a set of options. - - >>> vtor.check('option("yoda", "jedi")', 'yoda') - 'yoda' - >>> vtor.check('option("yoda", "jedi")', 'jed') - Traceback (most recent call last): - VdtValueError: the value "jed" is unacceptable. - >>> vtor.check('option("yoda", "jedi")', 0) - Traceback (most recent call last): - VdtTypeError: the value "0" is of the wrong type. - """ - if not isinstance(value, string_type): - raise VdtTypeError(value) - if not value in options: - raise VdtValueError(value) - return value - - -def _test(value, *args, **keywargs): - """ - A function that exists for test purposes. - - >>> checks = [ - ... '3, 6, min=1, max=3, test=list(a, b, c)', - ... '3', - ... '3, 6', - ... '3,', - ... 'min=1, test="a b c"', - ... 'min=5, test="a, b, c"', - ... 'min=1, max=3, test="a, b, c"', - ... 'min=-100, test=-99', - ... 'min=1, max=3', - ... '3, 6, test="36"', - ... '3, 6, test="a, b, c"', - ... '3, max=3, test=list("a", "b", "c")', - ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''', - ... "test='x=fish(3)'", - ... ] - >>> v = Validator({'test': _test}) - >>> for entry in checks: - ... pprint(v.check(('test(%s)' % entry), 3)) - (3, ('3', '6'), {'max': '3', 'min': '1', 'test': ['a', 'b', 'c']}) - (3, ('3',), {}) - (3, ('3', '6'), {}) - (3, ('3',), {}) - (3, (), {'min': '1', 'test': 'a b c'}) - (3, (), {'min': '5', 'test': 'a, b, c'}) - (3, (), {'max': '3', 'min': '1', 'test': 'a, b, c'}) - (3, (), {'min': '-100', 'test': '-99'}) - (3, (), {'max': '3', 'min': '1'}) - (3, ('3', '6'), {'test': '36'}) - (3, ('3', '6'), {'test': 'a, b, c'}) - (3, ('3',), {'max': '3', 'test': ['a', 'b', 'c']}) - (3, ('3',), {'max': '3', 'test': ["'a'", 'b', 'x=(c)']}) - (3, (), {'test': 'x=fish(3)'}) - - >>> v = Validator() - >>> v.check('integer(default=6)', '3') - 3 - >>> v.check('integer(default=6)', None, True) - 6 - >>> v.get_default_value('integer(default=6)') - 6 - >>> v.get_default_value('float(default=6)') - 6.0 - >>> v.get_default_value('pass(default=None)') - >>> v.get_default_value("string(default='None')") - 'None' - >>> v.get_default_value('pass') - Traceback (most recent call last): - KeyError: 'Check "pass" has no default value.' - >>> v.get_default_value('pass(default=list(1, 2, 3, 4))') - ['1', '2', '3', '4'] - - >>> v = Validator() - >>> v.check("pass(default=None)", None, True) - >>> v.check("pass(default='None')", None, True) - 'None' - >>> v.check('pass(default="None")', None, True) - 'None' - >>> v.check('pass(default=list(1, 2, 3, 4))', None, True) - ['1', '2', '3', '4'] - - Bug test for unicode arguments - >>> v = Validator() - >>> v.check(unicode('string(min=4)'), unicode('test')) == unicode('test') - True - - >>> v = Validator() - >>> v.get_default_value(unicode('string(min=4, default="1234")')) == unicode('1234') - True - >>> v.check(unicode('string(min=4, default="1234")'), unicode('test')) == unicode('test') - True - - >>> v = Validator() - >>> default = v.get_default_value('string(default=None)') - >>> default == None - 1 - """ - return (value, args, keywargs) - - -def _test2(): - """ - >>> - >>> v = Validator() - >>> v.get_default_value('string(default="#ff00dd")') - '#ff00dd' - >>> v.get_default_value('integer(default=3) # comment') - 3 - """ - -def _test3(): - r""" - >>> vtor.check('string(default="")', '', missing=True) - '' - >>> vtor.check('string(default="\n")', '', missing=True) - '\n' - >>> print(vtor.check('string(default="\n")', '', missing=True)) - - - >>> vtor.check('string()', '\n') - '\n' - >>> vtor.check('string(default="\n\n\n")', '', missing=True) - '\n\n\n' - >>> vtor.check('string()', 'random \n text goes here\n\n') - 'random \n text goes here\n\n' - >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")', - ... '', missing=True) - ' \nrandom text\ngoes \n here\n\n ' - >>> vtor.check("string(default='\n\n\n')", '', missing=True) - '\n\n\n' - >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True) - '\n' - >>> vtor.check("string_list()", ['foo', '\n', 'bar']) - ['foo', '\n', 'bar'] - >>> vtor.check("string_list(default=list('\n'))", '', missing=True) - ['\n'] - """ - - -if __name__ == '__main__': - # run the code tests in doctest format - import sys - import doctest - m = sys.modules.get('__main__') - globs = m.__dict__.copy() - globs.update({ - 'vtor': Validator(), - }) - - failures, tests = doctest.testmod( - m, globs=globs, - optionflags=doctest.IGNORE_EXCEPTION_DETAIL | doctest.ELLIPSIS) - print('{} {} failures out of {} tests' - .format("FAIL" if failures else "*OK*", failures, tests)) - sys.exit(bool(failures)) diff --git a/libs/common/decorator.py b/libs/common/decorator.py index 44303eed..2479b6f7 100644 --- a/libs/common/decorator.py +++ b/libs/common/decorator.py @@ -1,6 +1,6 @@ # ######################### LICENSE ############################ # -# Copyright (c) 2005-2018, Michele Simionato +# Copyright (c) 2005-2021, Michele Simionato # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -28,49 +28,26 @@ # DAMAGE. """ -Decorator module, see http://pypi.python.org/pypi/decorator +Decorator module, see +https://github.com/micheles/decorator/blob/master/docs/documentation.md for the documentation. """ -from __future__ import print_function - import re import sys import inspect import operator import itertools -import collections - -__version__ = '4.3.0' - -if sys.version >= '3': - from inspect import getfullargspec - - def get_init(cls): - return cls.__init__ -else: - FullArgSpec = collections.namedtuple( - 'FullArgSpec', 'args varargs varkw defaults ' - 'kwonlyargs kwonlydefaults annotations') - - def getfullargspec(f): - "A quick and dirty replacement for getfullargspec for Python 2.X" - return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) - - def get_init(cls): - return cls.__init__.__func__ - -try: - iscoroutinefunction = inspect.iscoroutinefunction -except AttributeError: - # let's assume there are no coroutine functions in old Python - def iscoroutinefunction(f): - return False +from contextlib import _GeneratorContextManager +from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction +__version__ = '5.1.1' DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') +POS = inspect.Parameter.POSITIONAL_OR_KEYWORD +EMPTY = inspect.Parameter.empty -# basic functionality +# this is not used anymore in the core, but kept for backward compatibility class FunctionMaker(object): """ An object with the ability to create functions with a given signature. @@ -94,7 +71,7 @@ class FunctionMaker(object): self.name = '_lambda_' self.doc = func.__doc__ self.module = func.__module__ - if inspect.isfunction(func): + if inspect.isroutine(func): argspec = getfullargspec(func) self.annotations = getattr(func, '__annotations__', {}) for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', @@ -137,7 +114,9 @@ class FunctionMaker(object): raise TypeError('You are decorating a non function: %s' % func) def update(self, func, **kw): - "Update the signature of func with the data in self" + """ + Update the signature of func with the data in self + """ func.__name__ = self.name func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) @@ -154,7 +133,9 @@ class FunctionMaker(object): func.__dict__.update(kw) def make(self, src_templ, evaldict=None, addsource=False, **attrs): - "Make a new function from a given template and update the signature" + """ + Make a new function from a given template and update the signature + """ src = src_templ % vars(self) # expand name and signature evaldict = evaldict or {} mo = DEF.search(src) @@ -173,7 +154,7 @@ class FunctionMaker(object): # Ensure each generated function has a unique filename for profilers # (such as cProfile) that depend on the tuple of (, # , ) being unique. - filename = '' % (next(self._compile_count),) + filename = '' % next(self._compile_count) try: code = compile(src, filename, 'single') exec(code, evaldict) @@ -215,90 +196,128 @@ class FunctionMaker(object): return self.make(body, evaldict, addsource, **attrs) -def decorate(func, caller, extras=()): +def fix(args, kwargs, sig): """ - decorate(func, caller) decorates a function using a caller. + Fix args and kwargs to be consistent with the signature """ - evaldict = dict(_call_=caller, _func_=func) - es = '' - for i, extra in enumerate(extras): - ex = '_e%d_' % i - evaldict[ex] = extra - es += ex + ', ' - fun = FunctionMaker.create( - func, "return _call_(_func_, %s%%(shortsignature)s)" % es, - evaldict, __wrapped__=func) - if hasattr(func, '__qualname__'): - fun.__qualname__ = func.__qualname__ + ba = sig.bind(*args, **kwargs) + ba.apply_defaults() # needed for test_dan_schult + return ba.args, ba.kwargs + + +def decorate(func, caller, extras=(), kwsyntax=False): + """ + Decorates a function/generator/coroutine using a caller. + If kwsyntax is True calling the decorated functions with keyword + syntax will pass the named arguments inside the ``kw`` dictionary, + even if such argument are positional, similarly to what functools.wraps + does. By default kwsyntax is False and the the arguments are untouched. + """ + sig = inspect.signature(func) + if iscoroutinefunction(caller): + async def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + return await caller(func, *(extras + args), **kw) + elif isgeneratorfunction(caller): + def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + for res in caller(func, *(extras + args), **kw): + yield res + else: + def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + return caller(func, *(extras + args), **kw) + fun.__name__ = func.__name__ + fun.__doc__ = func.__doc__ + fun.__wrapped__ = func + fun.__signature__ = sig + fun.__qualname__ = func.__qualname__ + # builtin functions like defaultdict.__setitem__ lack many attributes + try: + fun.__defaults__ = func.__defaults__ + except AttributeError: + pass + try: + fun.__kwdefaults__ = func.__kwdefaults__ + except AttributeError: + pass + try: + fun.__annotations__ = func.__annotations__ + except AttributeError: + pass + try: + fun.__module__ = func.__module__ + except AttributeError: + pass + try: + fun.__dict__.update(func.__dict__) + except AttributeError: + pass return fun -def decorator(caller, _func=None): - """decorator(caller) converts a caller function into a decorator""" +def decoratorx(caller): + """ + A version of "decorator" implemented via "exec" and not via the + Signature object. Use this if you are want to preserve the `.__code__` + object properties (https://github.com/micheles/decorator/issues/129). + """ + def dec(func): + return FunctionMaker.create( + func, + "return _call_(_func_, %(shortsignature)s)", + dict(_call_=caller, _func_=func), + __wrapped__=func, __qualname__=func.__qualname__) + return dec + + +def decorator(caller, _func=None, kwsyntax=False): + """ + decorator(caller) converts a caller function into a decorator + """ if _func is not None: # return a decorated function # this is obsolete behavior; you should use decorate instead - return decorate(_func, caller) + return decorate(_func, caller, (), kwsyntax) # else return a decorator function - defaultargs, defaults = '', () - if inspect.isclass(caller): - name = caller.__name__.lower() - doc = 'decorator(%s) converts functions/generators into ' \ - 'factories of %s objects' % (caller.__name__, caller.__name__) - elif inspect.isfunction(caller): - if caller.__name__ == '': - name = '_lambda_' + sig = inspect.signature(caller) + dec_params = [p for p in sig.parameters.values() if p.kind is POS] + + def dec(func=None, *args, **kw): + na = len(args) + 1 + extras = args + tuple(kw.get(p.name, p.default) + for p in dec_params[na:] + if p.default is not EMPTY) + if func is None: + return lambda func: decorate(func, caller, extras, kwsyntax) else: - name = caller.__name__ - doc = caller.__doc__ - nargs = caller.__code__.co_argcount - ndefs = len(caller.__defaults__ or ()) - defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) - if defaultargs: - defaultargs += ',' - defaults = caller.__defaults__ - else: # assume caller is an object with a __call__ method - name = caller.__class__.__name__.lower() - doc = caller.__call__.__doc__ - evaldict = dict(_call=caller, _decorate_=decorate) - dec = FunctionMaker.create( - '%s(%s func)' % (name, defaultargs), - 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' - 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), - evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) - if defaults: - dec.__defaults__ = defaults + (None,) + return decorate(func, caller, extras, kwsyntax) + dec.__signature__ = sig.replace(parameters=dec_params) + dec.__name__ = caller.__name__ + dec.__doc__ = caller.__doc__ + dec.__wrapped__ = caller + dec.__qualname__ = caller.__qualname__ + dec.__kwdefaults__ = getattr(caller, '__kwdefaults__', None) + dec.__dict__.update(caller.__dict__) return dec # ####################### contextmanager ####################### # -try: # Python >= 3.2 - from contextlib import _GeneratorContextManager -except ImportError: # Python >= 2.5 - from contextlib import GeneratorContextManager as _GeneratorContextManager - class ContextManager(_GeneratorContextManager): + def __init__(self, g, *a, **k): + _GeneratorContextManager.__init__(self, g, a, k) + def __call__(self, func): - """Context manager decorator""" - return FunctionMaker.create( - func, "with _self_: return _func_(%(shortsignature)s)", - dict(_self_=self, _func_=func), __wrapped__=func) + def caller(f, *a, **k): + with self.__class__(self.func, *self.args, **self.kwds): + return f(*a, **k) + return decorate(func, caller) -init = getfullargspec(_GeneratorContextManager.__init__) -n_args = len(init.args) -if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g(*a, **k)) - ContextManager.__init__ = __init__ -elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 - pass -elif n_args == 4: # (self, gen, args, kwds) Python 3.5 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g, a, k) - ContextManager.__init__ = __init__ - _contextmanager = decorator(ContextManager) diff --git a/libs/common/dogpile/__init__.py b/libs/common/dogpile/__init__.py index fc8fd452..2367ae03 100644 --- a/libs/common/dogpile/__init__.py +++ b/libs/common/dogpile/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.7.1' +__version__ = "1.1.8" from .lock import Lock # noqa from .lock import NeedRegenerationException # noqa diff --git a/libs/common/dogpile/cache/__init__.py b/libs/common/dogpile/cache/__init__.py index fb57cbcc..91c3a82c 100644 --- a/libs/common/dogpile/cache/__init__.py +++ b/libs/common/dogpile/cache/__init__.py @@ -1,4 +1,6 @@ -from .region import CacheRegion, register_backend, make_region # noqa +from .region import CacheRegion # noqa +from .region import make_region # noqa +from .region import register_backend # noqa +from .. import __version__ # noqa # backwards compat -from .. import __version__ # noqa diff --git a/libs/common/dogpile/cache/api.py b/libs/common/dogpile/cache/api.py index d66e5a70..0717d439 100644 --- a/libs/common/dogpile/cache/api.py +++ b/libs/common/dogpile/cache/api.py @@ -1,14 +1,22 @@ -import operator -from ..util.compat import py3k +import abc +import pickle +from typing import Any +from typing import Callable +from typing import cast +from typing import Mapping +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Union -class NoValue(object): +class NoValue: """Describe a missing cache value. - The :attr:`.NO_VALUE` module global - should be used. + The :data:`.NO_VALUE` constant should be used. """ + @property def payload(self): return self @@ -18,49 +26,125 @@ class NoValue(object): fill another cache key. """ - return '' + return "" - if py3k: - def __bool__(self): # pragma NO COVERAGE - return False - else: - def __nonzero__(self): # pragma NO COVERAGE - return False + def __bool__(self): # pragma NO COVERAGE + return False NO_VALUE = NoValue() """Value returned from ``get()`` that describes a key not present.""" +MetaDataType = Mapping[str, Any] -class CachedValue(tuple): + +KeyType = str +"""A cache key.""" + +ValuePayload = Any +"""An object to be placed in the cache against a key.""" + + +KeyManglerType = Callable[[KeyType], KeyType] +Serializer = Callable[[ValuePayload], bytes] +Deserializer = Callable[[bytes], ValuePayload] + + +class CacheMutex(abc.ABC): + """Describes a mutexing object with acquire and release methods. + + This is an abstract base class; any object that has acquire/release + methods may be used. + + .. versionadded:: 1.1 + + + .. seealso:: + + :meth:`.CacheBackend.get_mutex` - the backend method that optionally + returns this locking object. + + """ + + @abc.abstractmethod + def acquire(self, wait: bool = True) -> bool: + """Acquire the mutex. + + :param wait: if True, block until available, else return True/False + immediately. + + :return: True if the lock succeeded. + + """ + raise NotImplementedError() + + @abc.abstractmethod + def release(self) -> None: + """Release the mutex.""" + + raise NotImplementedError() + + @abc.abstractmethod + def locked(self) -> bool: + """Check if the mutex was acquired. + + :return: true if the lock is acquired. + + .. versionadded:: 1.1.2 + + """ + raise NotImplementedError() + + @classmethod + def __subclasshook__(cls, C): + return hasattr(C, "acquire") and hasattr(C, "release") + + +class CachedValue(NamedTuple): """Represent a value stored in the cache. :class:`.CachedValue` is a two-tuple of ``(payload, metadata)``, where ``metadata`` is dogpile.cache's tracking information ( - currently the creation time). The metadata - and tuple structure is pickleable, if - the backend requires serialization. + currently the creation time). """ - payload = property(operator.itemgetter(0)) - """Named accessor for the payload.""" - metadata = property(operator.itemgetter(1)) - """Named accessor for the dogpile.cache metadata dictionary.""" + payload: ValuePayload - def __new__(cls, payload, metadata): - return tuple.__new__(cls, (payload, metadata)) - - def __reduce__(self): - return CachedValue, (self.payload, self.metadata) + metadata: MetaDataType -class CacheBackend(object): - """Base class for backend implementations.""" +CacheReturnType = Union[CachedValue, NoValue] +"""The non-serialized form of what may be returned from a backend +get method. - key_mangler = None +""" + +SerializedReturnType = Union[bytes, NoValue] +"""the serialized form of what may be returned from a backend get method.""" + +BackendFormatted = Union[CacheReturnType, SerializedReturnType] +"""Describes the type returned from the :meth:`.CacheBackend.get` method.""" + +BackendSetType = Union[CachedValue, bytes] +"""Describes the value argument passed to the :meth:`.CacheBackend.set` +method.""" + +BackendArguments = Mapping[str, Any] + + +class CacheBackend: + """Base class for backend implementations. + + Backends which set and get Python object values should subclass this + backend. For backends in which the value that's stored is ultimately + a stream of bytes, the :class:`.BytesBackend` should be used. + + """ + + key_mangler: Optional[Callable[[KeyType], KeyType]] = None """Key mangling function. May be None, or otherwise declared @@ -68,7 +152,23 @@ class CacheBackend(object): """ - def __init__(self, arguments): # pragma NO COVERAGE + serializer: Union[None, Serializer] = None + """Serializer function that will be used by default if not overridden + by the region. + + .. versionadded:: 1.1 + + """ + + deserializer: Union[None, Deserializer] = None + """deserializer function that will be used by default if not overridden + by the region. + + .. versionadded:: 1.1 + + """ + + def __init__(self, arguments: BackendArguments): # pragma NO COVERAGE """Construct a new :class:`.CacheBackend`. Subclasses should override this to @@ -91,10 +191,10 @@ class CacheBackend(object): ) ) - def has_lock_timeout(self): + def has_lock_timeout(self) -> bool: return False - def get_mutex(self, key): + def get_mutex(self, key: KeyType) -> Optional[CacheMutex]: """Return an optional mutexing object for the given key. This object need only provide an ``acquire()`` @@ -127,48 +227,141 @@ class CacheBackend(object): """ return None - def get(self, key): # pragma NO COVERAGE - """Retrieve a value from the cache. + def get(self, key: KeyType) -> BackendFormatted: # pragma NO COVERAGE + """Retrieve an optionally serialized value from the cache. - The returned value should be an instance of - :class:`.CachedValue`, or ``NO_VALUE`` if - not present. + :param key: String key that was passed to the :meth:`.CacheRegion.get` + method, which will also be processed by the "key mangling" function + if one was present. + + :return: the Python object that corresponds to + what was established via the :meth:`.CacheBackend.set` method, + or the :data:`.NO_VALUE` constant if not present. + + If a serializer is in use, this method will only be called if the + :meth:`.CacheBackend.get_serialized` method is not overridden. """ raise NotImplementedError() - def get_multi(self, keys): # pragma NO COVERAGE - """Retrieve multiple values from the cache. + def get_multi( + self, keys: Sequence[KeyType] + ) -> Sequence[BackendFormatted]: # pragma NO COVERAGE + """Retrieve multiple optionally serialized values from the cache. - The returned value should be a list, corresponding - to the list of keys given. + :param keys: sequence of string keys that was passed to the + :meth:`.CacheRegion.get_multi` method, which will also be processed + by the "key mangling" function if one was present. + + :return a list of values as would be returned + individually via the :meth:`.CacheBackend.get` method, corresponding + to the list of keys given. + + If a serializer is in use, this method will only be called if the + :meth:`.CacheBackend.get_serialized_multi` method is not overridden. .. versionadded:: 0.5.0 """ raise NotImplementedError() - def set(self, key, value): # pragma NO COVERAGE - """Set a value in the cache. + def get_serialized(self, key: KeyType) -> SerializedReturnType: + """Retrieve a serialized value from the cache. - The key will be whatever was passed - to the registry, processed by the - "key mangling" function, if any. - The value will always be an instance - of :class:`.CachedValue`. + :param key: String key that was passed to the :meth:`.CacheRegion.get` + method, which will also be processed by the "key mangling" function + if one was present. + + :return: a bytes object, or :data:`.NO_VALUE` + constant if not present. + + The default implementation of this method for :class:`.CacheBackend` + returns the value of the :meth:`.CacheBackend.get` method. + + .. versionadded:: 1.1 + + .. seealso:: + + :class:`.BytesBackend` + + """ + return cast(SerializedReturnType, self.get(key)) + + def get_serialized_multi( + self, keys: Sequence[KeyType] + ) -> Sequence[SerializedReturnType]: # pragma NO COVERAGE + """Retrieve multiple serialized values from the cache. + + :param keys: sequence of string keys that was passed to the + :meth:`.CacheRegion.get_multi` method, which will also be processed + by the "key mangling" function if one was present. + + :return: list of bytes objects + + The default implementation of this method for :class:`.CacheBackend` + returns the value of the :meth:`.CacheBackend.get_multi` method. + + .. versionadded:: 1.1 + + .. seealso:: + + :class:`.BytesBackend` + + """ + return cast(Sequence[SerializedReturnType], self.get_multi(keys)) + + def set( + self, key: KeyType, value: BackendSetType + ) -> None: # pragma NO COVERAGE + """Set an optionally serialized value in the cache. + + :param key: String key that was passed to the :meth:`.CacheRegion.set` + method, which will also be processed by the "key mangling" function + if one was present. + + :param value: The optionally serialized :class:`.CachedValue` object. + May be an instance of :class:`.CachedValue` or a bytes object + depending on if a serializer is in use with the region and if the + :meth:`.CacheBackend.set_serialized` method is not overridden. + + .. seealso:: + + :meth:`.CacheBackend.set_serialized` """ raise NotImplementedError() - def set_multi(self, mapping): # pragma NO COVERAGE + def set_serialized( + self, key: KeyType, value: bytes + ) -> None: # pragma NO COVERAGE + """Set a serialized value in the cache. + + :param key: String key that was passed to the :meth:`.CacheRegion.set` + method, which will also be processed by the "key mangling" function + if one was present. + + :param value: a bytes object to be stored. + + The default implementation of this method for :class:`.CacheBackend` + calls upon the :meth:`.CacheBackend.set` method. + + .. versionadded:: 1.1 + + .. seealso:: + + :class:`.BytesBackend` + + """ + self.set(key, value) + + def set_multi( + self, mapping: Mapping[KeyType, BackendSetType] + ) -> None: # pragma NO COVERAGE """Set multiple values in the cache. - ``mapping`` is a dict in which - the key will be whatever was passed - to the registry, processed by the - "key mangling" function, if any. - The value will always be an instance - of :class:`.CachedValue`. + :param mapping: a dict in which the key will be whatever was passed to + the :meth:`.CacheRegion.set_multi` method, processed by the "key + mangling" function, if any. When implementing a new :class:`.CacheBackend` or cutomizing via :class:`.ProxyBackend`, be aware that when this method is invoked by @@ -178,17 +371,52 @@ class CacheBackend(object): -- that will have the undesirable effect of modifying the returned values as well. + If a serializer is in use, this method will only be called if the + :meth:`.CacheBackend.set_serialized_multi` method is not overridden. + + .. versionadded:: 0.5.0 """ raise NotImplementedError() - def delete(self, key): # pragma NO COVERAGE + def set_serialized_multi( + self, mapping: Mapping[KeyType, bytes] + ) -> None: # pragma NO COVERAGE + """Set multiple serialized values in the cache. + + :param mapping: a dict in which the key will be whatever was passed to + the :meth:`.CacheRegion.set_multi` method, processed by the "key + mangling" function, if any. + + When implementing a new :class:`.CacheBackend` or cutomizing via + :class:`.ProxyBackend`, be aware that when this method is invoked by + :meth:`.Region.get_or_create_multi`, the ``mapping`` values are the + same ones returned to the upstream caller. If the subclass alters the + values in any way, it must not do so 'in-place' on the ``mapping`` dict + -- that will have the undesirable effect of modifying the returned + values as well. + + .. versionadded:: 1.1 + + The default implementation of this method for :class:`.CacheBackend` + calls upon the :meth:`.CacheBackend.set_multi` method. + + .. seealso:: + + :class:`.BytesBackend` + + + """ + self.set_multi(mapping) + + def delete(self, key: KeyType) -> None: # pragma NO COVERAGE """Delete a value from the cache. - The key will be whatever was passed - to the registry, processed by the - "key mangling" function, if any. + :param key: String key that was passed to the + :meth:`.CacheRegion.delete` + method, which will also be processed by the "key mangling" function + if one was present. The behavior here should be idempotent, that is, can be called any number of times @@ -197,12 +425,14 @@ class CacheBackend(object): """ raise NotImplementedError() - def delete_multi(self, keys): # pragma NO COVERAGE + def delete_multi( + self, keys: Sequence[KeyType] + ) -> None: # pragma NO COVERAGE """Delete multiple values from the cache. - The key will be whatever was passed - to the registry, processed by the - "key mangling" function, if any. + :param keys: sequence of string keys that was passed to the + :meth:`.CacheRegion.delete_multi` method, which will also be processed + by the "key mangling" function if one was present. The behavior here should be idempotent, that is, can be called any number of times @@ -213,3 +443,95 @@ class CacheBackend(object): """ raise NotImplementedError() + + +class DefaultSerialization: + serializer: Union[None, Serializer] = staticmethod( # type: ignore + pickle.dumps + ) + deserializer: Union[None, Deserializer] = staticmethod( # type: ignore + pickle.loads + ) + + +class BytesBackend(DefaultSerialization, CacheBackend): + """A cache backend that receives and returns series of bytes. + + This backend only supports the "serialized" form of values; subclasses + should implement :meth:`.BytesBackend.get_serialized`, + :meth:`.BytesBackend.get_serialized_multi`, + :meth:`.BytesBackend.set_serialized`, + :meth:`.BytesBackend.set_serialized_multi`. + + .. versionadded:: 1.1 + + """ + + def get_serialized(self, key: KeyType) -> SerializedReturnType: + """Retrieve a serialized value from the cache. + + :param key: String key that was passed to the :meth:`.CacheRegion.get` + method, which will also be processed by the "key mangling" function + if one was present. + + :return: a bytes object, or :data:`.NO_VALUE` + constant if not present. + + .. versionadded:: 1.1 + + """ + raise NotImplementedError() + + def get_serialized_multi( + self, keys: Sequence[KeyType] + ) -> Sequence[SerializedReturnType]: # pragma NO COVERAGE + """Retrieve multiple serialized values from the cache. + + :param keys: sequence of string keys that was passed to the + :meth:`.CacheRegion.get_multi` method, which will also be processed + by the "key mangling" function if one was present. + + :return: list of bytes objects + + .. versionadded:: 1.1 + + """ + raise NotImplementedError() + + def set_serialized( + self, key: KeyType, value: bytes + ) -> None: # pragma NO COVERAGE + """Set a serialized value in the cache. + + :param key: String key that was passed to the :meth:`.CacheRegion.set` + method, which will also be processed by the "key mangling" function + if one was present. + + :param value: a bytes object to be stored. + + .. versionadded:: 1.1 + + """ + raise NotImplementedError() + + def set_serialized_multi( + self, mapping: Mapping[KeyType, bytes] + ) -> None: # pragma NO COVERAGE + """Set multiple serialized values in the cache. + + :param mapping: a dict in which the key will be whatever was passed to + the :meth:`.CacheRegion.set_multi` method, processed by the "key + mangling" function, if any. + + When implementing a new :class:`.CacheBackend` or cutomizing via + :class:`.ProxyBackend`, be aware that when this method is invoked by + :meth:`.Region.get_or_create_multi`, the ``mapping`` values are the + same ones returned to the upstream caller. If the subclass alters the + values in any way, it must not do so 'in-place' on the ``mapping`` dict + -- that will have the undesirable effect of modifying the returned + values as well. + + .. versionadded:: 1.1 + + """ + raise NotImplementedError() diff --git a/libs/common/dogpile/cache/backends/__init__.py b/libs/common/dogpile/cache/backends/__init__.py index 041f05a3..e3d90400 100644 --- a/libs/common/dogpile/cache/backends/__init__.py +++ b/libs/common/dogpile/cache/backends/__init__.py @@ -1,22 +1,47 @@ -from dogpile.cache.region import register_backend +from ...util import PluginLoader + +_backend_loader = PluginLoader("dogpile.cache") +register_backend = _backend_loader.register register_backend( - "dogpile.cache.null", "dogpile.cache.backends.null", "NullBackend") + "dogpile.cache.null", "dogpile.cache.backends.null", "NullBackend" +) register_backend( - "dogpile.cache.dbm", "dogpile.cache.backends.file", "DBMBackend") + "dogpile.cache.dbm", "dogpile.cache.backends.file", "DBMBackend" +) register_backend( - "dogpile.cache.pylibmc", "dogpile.cache.backends.memcached", - "PylibmcBackend") + "dogpile.cache.pylibmc", + "dogpile.cache.backends.memcached", + "PylibmcBackend", +) register_backend( - "dogpile.cache.bmemcached", "dogpile.cache.backends.memcached", - "BMemcachedBackend") + "dogpile.cache.bmemcached", + "dogpile.cache.backends.memcached", + "BMemcachedBackend", +) register_backend( - "dogpile.cache.memcached", "dogpile.cache.backends.memcached", - "MemcachedBackend") + "dogpile.cache.memcached", + "dogpile.cache.backends.memcached", + "MemcachedBackend", +) register_backend( - "dogpile.cache.memory", "dogpile.cache.backends.memory", "MemoryBackend") + "dogpile.cache.pymemcache", + "dogpile.cache.backends.memcached", + "PyMemcacheBackend", +) register_backend( - "dogpile.cache.memory_pickle", "dogpile.cache.backends.memory", - "MemoryPickleBackend") + "dogpile.cache.memory", "dogpile.cache.backends.memory", "MemoryBackend" +) register_backend( - "dogpile.cache.redis", "dogpile.cache.backends.redis", "RedisBackend") + "dogpile.cache.memory_pickle", + "dogpile.cache.backends.memory", + "MemoryPickleBackend", +) +register_backend( + "dogpile.cache.redis", "dogpile.cache.backends.redis", "RedisBackend" +) +register_backend( + "dogpile.cache.redis_sentinel", + "dogpile.cache.backends.redis", + "RedisSentinelBackend", +) diff --git a/libs/common/dogpile/cache/backends/file.py b/libs/common/dogpile/cache/backends/file.py index 309c055a..bc52d8bc 100644 --- a/libs/common/dogpile/cache/backends/file.py +++ b/libs/common/dogpile/cache/backends/file.py @@ -7,16 +7,20 @@ Provides backends that deal with local filesystem access. """ from __future__ import with_statement -from ..api import CacheBackend, NO_VALUE + from contextlib import contextmanager -from ...util import compat -from ... import util +import dbm import os +import threading -__all__ = 'DBMBackend', 'FileLock', 'AbstractFileLock' +from ..api import BytesBackend +from ..api import NO_VALUE +from ... import util + +__all__ = ["DBMBackend", "FileLock", "AbstractFileLock"] -class DBMBackend(CacheBackend): +class DBMBackend(BytesBackend): """A file-backend using a dbm file to store keys. Basic usage:: @@ -134,28 +138,25 @@ class DBMBackend(CacheBackend): """ + def __init__(self, arguments): self.filename = os.path.abspath( - os.path.normpath(arguments['filename']) + os.path.normpath(arguments["filename"]) ) dir_, filename = os.path.split(self.filename) self.lock_factory = arguments.get("lock_factory", FileLock) self._rw_lock = self._init_lock( - arguments.get('rw_lockfile'), - ".rw.lock", dir_, filename) + arguments.get("rw_lockfile"), ".rw.lock", dir_, filename + ) self._dogpile_lock = self._init_lock( - arguments.get('dogpile_lockfile'), + arguments.get("dogpile_lockfile"), ".dogpile.lock", - dir_, filename, - util.KeyReentrantMutex.factory) + dir_, + filename, + util.KeyReentrantMutex.factory, + ) - # TODO: make this configurable - if compat.py3k: - import dbm - else: - import anydbm as dbm - self.dbmmodule = dbm self._init_dbm_file() def _init_lock(self, argument, suffix, basedir, basefile, wrapper=None): @@ -163,9 +164,8 @@ class DBMBackend(CacheBackend): lock = self.lock_factory(os.path.join(basedir, basefile + suffix)) elif argument is not False: lock = self.lock_factory( - os.path.abspath( - os.path.normpath(argument) - )) + os.path.abspath(os.path.normpath(argument)) + ) else: return None if wrapper: @@ -175,12 +175,12 @@ class DBMBackend(CacheBackend): def _init_dbm_file(self): exists = os.access(self.filename, os.F_OK) if not exists: - for ext in ('db', 'dat', 'pag', 'dir'): + for ext in ("db", "dat", "pag", "dir"): if os.access(self.filename + os.extsep + ext, os.F_OK): exists = True break if not exists: - fh = self.dbmmodule.open(self.filename, 'c') + fh = dbm.open(self.filename, "c") fh.close() def get_mutex(self, key): @@ -210,57 +210,50 @@ class DBMBackend(CacheBackend): @contextmanager def _dbm_file(self, write): with self._use_rw_lock(write): - dbm = self.dbmmodule.open( - self.filename, - "w" if write else "r") - yield dbm - dbm.close() + with dbm.open(self.filename, "w" if write else "r") as dbm_obj: + yield dbm_obj - def get(self, key): - with self._dbm_file(False) as dbm: - if hasattr(dbm, 'get'): - value = dbm.get(key, NO_VALUE) + def get_serialized(self, key): + with self._dbm_file(False) as dbm_obj: + if hasattr(dbm_obj, "get"): + value = dbm_obj.get(key, NO_VALUE) else: # gdbm objects lack a .get method try: - value = dbm[key] + value = dbm_obj[key] except KeyError: value = NO_VALUE - if value is not NO_VALUE: - value = compat.pickle.loads(value) return value - def get_multi(self, keys): - return [self.get(key) for key in keys] + def get_serialized_multi(self, keys): + return [self.get_serialized(key) for key in keys] - def set(self, key, value): - with self._dbm_file(True) as dbm: - dbm[key] = compat.pickle.dumps(value, - compat.pickle.HIGHEST_PROTOCOL) + def set_serialized(self, key, value): + with self._dbm_file(True) as dbm_obj: + dbm_obj[key] = value - def set_multi(self, mapping): - with self._dbm_file(True) as dbm: + def set_serialized_multi(self, mapping): + with self._dbm_file(True) as dbm_obj: for key, value in mapping.items(): - dbm[key] = compat.pickle.dumps(value, - compat.pickle.HIGHEST_PROTOCOL) + dbm_obj[key] = value def delete(self, key): - with self._dbm_file(True) as dbm: + with self._dbm_file(True) as dbm_obj: try: - del dbm[key] + del dbm_obj[key] except KeyError: pass def delete_multi(self, keys): - with self._dbm_file(True) as dbm: + with self._dbm_file(True) as dbm_obj: for key in keys: try: - del dbm[key] + del dbm_obj[key] except KeyError: pass -class AbstractFileLock(object): +class AbstractFileLock: """Coordinate read/write access to a file. typically is a file-based lock but doesn't necessarily have to be. @@ -392,17 +385,18 @@ class FileLock(AbstractFileLock): """ def __init__(self, filename): - self._filedescriptor = compat.threading.local() + self._filedescriptor = threading.local() self.filename = filename @util.memoized_property def _module(self): import fcntl + return fcntl @property def is_open(self): - return hasattr(self._filedescriptor, 'fileno') + return hasattr(self._filedescriptor, "fileno") def acquire_read_lock(self, wait): return self._acquire(wait, os.O_RDONLY, self._module.LOCK_SH) diff --git a/libs/common/dogpile/cache/backends/memcached.py b/libs/common/dogpile/cache/backends/memcached.py index 6758a998..2fe30c7a 100644 --- a/libs/common/dogpile/cache/backends/memcached.py +++ b/libs/common/dogpile/cache/backends/memcached.py @@ -6,23 +6,43 @@ Provides backends for talking to `memcached `_. """ -from ..api import CacheBackend, NO_VALUE -from ...util import compat -from ... import util import random +import threading import time +import typing +from typing import Any +from typing import Mapping +import warnings -__all__ = 'GenericMemcachedBackend', 'MemcachedBackend',\ - 'PylibmcBackend', 'BMemcachedBackend', 'MemcachedLock' +from ..api import CacheBackend +from ..api import NO_VALUE +from ... import util + + +if typing.TYPE_CHECKING: + import bmemcached + import memcache + import pylibmc + import pymemcache +else: + # delayed import + bmemcached = None # noqa F811 + memcache = None # noqa F811 + pylibmc = None # noqa F811 + pymemcache = None # noqa F811 + +__all__ = ( + "GenericMemcachedBackend", + "MemcachedBackend", + "PylibmcBackend", + "PyMemcacheBackend", + "BMemcachedBackend", + "MemcachedLock", +) class MemcachedLock(object): - """Simple distributed lock using memcached. - - This is an adaptation of the lock featured at - http://amix.dk/blog/post/19386 - - """ + """Simple distributed lock using memcached.""" def __init__(self, client_fn, key, timeout=0): self.client_fn = client_fn @@ -38,11 +58,15 @@ class MemcachedLock(object): elif not wait: return False else: - sleep_time = (((i + 1) * random.random()) + 2 ** i) / 2.5 + sleep_time = (((i + 1) * random.random()) + 2**i) / 2.5 time.sleep(sleep_time) if i < 15: i += 1 + def locked(self): + client = self.client_fn() + return client.get(self.key) is not None + def release(self): client = self.client_fn() client.delete(self.key) @@ -100,10 +124,17 @@ class GenericMemcachedBackend(CacheBackend): """ - set_arguments = {} + set_arguments: Mapping[str, Any] = {} """Additional arguments which will be passed to the :meth:`set` method.""" + # No need to override serializer, as all the memcached libraries + # handles that themselves. Still, we support customizing the + # serializer/deserializer to use better default pickle protocol + # or completely different serialization mechanism + serializer = None + deserializer = None + def __init__(self, arguments): self._imports() # using a plain threading.local here. threading.local @@ -111,11 +142,10 @@ class GenericMemcachedBackend(CacheBackend): # so the idea is that this is superior to pylibmc's # own ThreadMappedPool which doesn't handle this # automatically. - self.url = util.to_list(arguments['url']) - self.distributed_lock = arguments.get('distributed_lock', False) - self.lock_timeout = arguments.get('lock_timeout', 0) - self.memcached_expire_time = arguments.get( - 'memcached_expire_time', 0) + self.url = util.to_list(arguments["url"]) + self.distributed_lock = arguments.get("distributed_lock", False) + self.lock_timeout = arguments.get("lock_timeout", 0) + self.memcached_expire_time = arguments.get("memcached_expire_time", 0) def has_lock_timeout(self): return self.lock_timeout != 0 @@ -132,7 +162,7 @@ class GenericMemcachedBackend(CacheBackend): def _clients(self): backend = self - class ClientPool(compat.threading.local): + class ClientPool(threading.local): def __init__(self): self.memcached = backend._create_client() @@ -152,8 +182,9 @@ class GenericMemcachedBackend(CacheBackend): def get_mutex(self, key): if self.distributed_lock: - return MemcachedLock(lambda: self.client, key, - timeout=self.lock_timeout) + return MemcachedLock( + lambda: self.client, key, timeout=self.lock_timeout + ) else: return None @@ -166,23 +197,18 @@ class GenericMemcachedBackend(CacheBackend): def get_multi(self, keys): values = self.client.get_multi(keys) + return [ - NO_VALUE if key not in values - else values[key] for key in keys + NO_VALUE if val is None else val + for val in [values.get(key, NO_VALUE) for key in keys] ] def set(self, key, value): - self.client.set( - key, - value, - **self.set_arguments - ) + self.client.set(key, value, **self.set_arguments) def set_multi(self, mapping): - self.client.set_multi( - mapping, - **self.set_arguments - ) + mapping = {key: value for key, value in mapping.items()} + self.client.set_multi(mapping, **self.set_arguments) def delete(self, key): self.client.delete(key) @@ -191,24 +217,23 @@ class GenericMemcachedBackend(CacheBackend): self.client.delete_multi(keys) -class MemcacheArgs(object): +class MemcacheArgs(GenericMemcachedBackend): """Mixin which provides support for the 'time' argument to set(), 'min_compress_len' to other methods. - """ + def __init__(self, arguments): - self.min_compress_len = arguments.get('min_compress_len', 0) + self.min_compress_len = arguments.get("min_compress_len", 0) self.set_arguments = {} if "memcached_expire_time" in arguments: self.set_arguments["time"] = arguments["memcached_expire_time"] if "min_compress_len" in arguments: - self.set_arguments["min_compress_len"] = \ - arguments["min_compress_len"] + self.set_arguments["min_compress_len"] = arguments[ + "min_compress_len" + ] super(MemcacheArgs, self).__init__(arguments) -pylibmc = None - class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): """A backend for the @@ -245,8 +270,8 @@ class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): """ def __init__(self, arguments): - self.binary = arguments.get('binary', False) - self.behaviors = arguments.get('behaviors', {}) + self.binary = arguments.get("binary", False) + self.behaviors = arguments.get("behaviors", {}) super(PylibmcBackend, self).__init__(arguments) def _imports(self): @@ -255,13 +280,9 @@ class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): def _create_client(self): return pylibmc.Client( - self.url, - binary=self.binary, - behaviors=self.behaviors + self.url, binary=self.binary, behaviors=self.behaviors ) -memcache = None - class MemcachedBackend(MemcacheArgs, GenericMemcachedBackend): """A backend using the standard @@ -281,16 +302,39 @@ class MemcachedBackend(MemcacheArgs, GenericMemcachedBackend): } ) + :param dead_retry: Number of seconds memcached server is considered dead + before it is tried again. Will be passed to ``memcache.Client`` + as the ``dead_retry`` parameter. + + .. versionchanged:: 1.1.8 Moved the ``dead_retry`` argument which was + erroneously added to "set_parameters" to + be part of the Memcached connection arguments. + + :param socket_timeout: Timeout in seconds for every call to a server. + Will be passed to ``memcache.Client`` as the ``socket_timeout`` + parameter. + + .. versionchanged:: 1.1.8 Moved the ``socket_timeout`` argument which + was erroneously added to "set_parameters" + to be part of the Memcached connection arguments. + """ + + def __init__(self, arguments): + self.dead_retry = arguments.get("dead_retry", 30) + self.socket_timeout = arguments.get("socket_timeout", 3) + super(MemcachedBackend, self).__init__(arguments) + def _imports(self): global memcache import memcache # noqa def _create_client(self): - return memcache.Client(self.url) - - -bmemcached = None + return memcache.Client( + self.url, + dead_retry=self.dead_retry, + socket_timeout=self.socket_timeout, + ) class BMemcachedBackend(GenericMemcachedBackend): @@ -299,9 +343,11 @@ class BMemcachedBackend(GenericMemcachedBackend): python-binary-memcached>`_ memcached client. - This is a pure Python memcached client which - includes the ability to authenticate with a memcached - server using SASL. + This is a pure Python memcached client which includes + security features like SASL and SSL/TLS. + + SASL is a standard for adding authentication mechanisms + to protocols in a way that is protocol independent. A typical configuration using username/password:: @@ -317,6 +363,25 @@ class BMemcachedBackend(GenericMemcachedBackend): } ) + A typical configuration using tls_context:: + + import ssl + from dogpile.cache import make_region + + ctx = ssl.create_default_context(cafile="/path/to/my-ca.pem") + + region = make_region().configure( + 'dogpile.cache.bmemcached', + expiration_time = 3600, + arguments = { + 'url':["127.0.0.1"], + 'tls_context':ctx, + } + ) + + For advanced ways to configure TLS creating a more complex + tls_context visit https://docs.python.org/3/library/ssl.html + Arguments which can be passed to the ``arguments`` dictionary include: @@ -324,11 +389,17 @@ class BMemcachedBackend(GenericMemcachedBackend): SASL authentication. :param password: optional password, will be used for SASL authentication. + :param tls_context: optional TLS context, will be used for + TLS connections. + + .. versionadded:: 1.0.2 """ + def __init__(self, arguments): - self.username = arguments.get('username', None) - self.password = arguments.get('password', None) + self.username = arguments.get("username", None) + self.password = arguments.get("password", None) + self.tls_context = arguments.get("tls_context", None) super(BMemcachedBackend, self).__init__(arguments) def _imports(self): @@ -345,7 +416,8 @@ class BMemcachedBackend(GenericMemcachedBackend): def add(self, key, value, timeout=0): try: return super(RepairBMemcachedAPI, self).add( - key, value, timeout) + key, value, timeout + ) except ValueError: return False @@ -355,10 +427,213 @@ class BMemcachedBackend(GenericMemcachedBackend): return self.Client( self.url, username=self.username, - password=self.password + password=self.password, + tls_context=self.tls_context, ) def delete_multi(self, keys): """python-binary-memcached api does not implements delete_multi""" for key in keys: self.delete(key) + + +class PyMemcacheBackend(GenericMemcachedBackend): + """A backend for the + `pymemcache `_ + memcached client. + + A comprehensive, fast, pure Python memcached client + + .. versionadded:: 1.1.2 + + pymemcache supports the following features: + + * Complete implementation of the memcached text protocol. + * Configurable timeouts for socket connect and send/recv calls. + * Access to the "noreply" flag, which can significantly increase + the speed of writes. + * Flexible, simple approach to serialization and deserialization. + * The (optional) ability to treat network and memcached errors as + cache misses. + + dogpile.cache uses the ``HashClient`` from pymemcache in order to reduce + API differences when compared to other memcached client drivers. + This allows the user to provide a single server or a list of memcached + servers. + + Arguments which can be passed to the ``arguments`` + dictionary include: + + :param tls_context: optional TLS context, will be used for + TLS connections. + + A typical configuration using tls_context:: + + import ssl + from dogpile.cache import make_region + + ctx = ssl.create_default_context(cafile="/path/to/my-ca.pem") + + region = make_region().configure( + 'dogpile.cache.pymemcache', + expiration_time = 3600, + arguments = { + 'url':["127.0.0.1"], + 'tls_context':ctx, + } + ) + + .. seealso:: + + ``_ - additional TLS + documentation. + + :param serde: optional "serde". Defaults to + ``pymemcache.serde.pickle_serde``. + + :param default_noreply: defaults to False. When set to True this flag + enables the pymemcache "noreply" feature. See the pymemcache + documentation for further details. + + :param socket_keepalive: optional socket keepalive, will be used for + TCP keepalive configuration. Use of this parameter requires pymemcache + 3.5.0 or greater. This parameter + accepts a + `pymemcache.client.base.KeepAliveOpts + `_ + object. + + A typical configuration using ``socket_keepalive``:: + + from pymemcache import KeepaliveOpts + from dogpile.cache import make_region + + # Using the default keepalive configuration + socket_keepalive = KeepaliveOpts() + + region = make_region().configure( + 'dogpile.cache.pymemcache', + expiration_time = 3600, + arguments = { + 'url':["127.0.0.1"], + 'socket_keepalive': socket_keepalive + } + ) + + .. versionadded:: 1.1.4 - added support for ``socket_keepalive``. + + :param enable_retry_client: optional flag to enable retry client + mechanisms to handle failure. Defaults to False. When set to ``True``, + the :paramref:`.PyMemcacheBackend.retry_attempts` parameter must also + be set, along with optional parameters + :paramref:`.PyMemcacheBackend.retry_delay`. + :paramref:`.PyMemcacheBackend.retry_for`, + :paramref:`.PyMemcacheBackend.do_not_retry_for`. + + .. seealso:: + + ``_ - + in the pymemcache documentation + + .. versionadded:: 1.1.4 + + :param retry_attempts: how many times to attempt an action with + pymemcache's retrying wrapper before failing. Must be 1 or above. + Defaults to None. + + .. versionadded:: 1.1.4 + + :param retry_delay: optional int|float, how many seconds to sleep between + each attempt. Used by the retry wrapper. Defaults to None. + + .. versionadded:: 1.1.4 + + :param retry_for: optional None|tuple|set|list, what exceptions to + allow retries for. Will allow retries for all exceptions if None. + Example: ``(MemcacheClientError, MemcacheUnexpectedCloseError)`` + Accepts any class that is a subclass of Exception. Defaults to None. + + .. versionadded:: 1.1.4 + + :param do_not_retry_for: optional None|tuple|set|list, what + exceptions should be retried. Will not block retries for any Exception if + None. Example: ``(IOError, MemcacheIllegalInputError)`` + Accepts any class that is a subclass of Exception. Defaults to None. + + .. versionadded:: 1.1.4 + + :param hashclient_retry_attempts: Amount of times a client should be tried + before it is marked dead and removed from the pool in the HashClient's + internal mechanisms. + + .. versionadded:: 1.1.5 + + :param hashclient_retry_timeout: Time in seconds that should pass between + retry attempts in the HashClient's internal mechanisms. + + .. versionadded:: 1.1.5 + + :param dead_timeout: Time in seconds before attempting to add a node + back in the pool in the HashClient's internal mechanisms. + + .. versionadded:: 1.1.5 + + """ # noqa E501 + + def __init__(self, arguments): + super().__init__(arguments) + + self.serde = arguments.get("serde", pymemcache.serde.pickle_serde) + self.default_noreply = arguments.get("default_noreply", False) + self.tls_context = arguments.get("tls_context", None) + self.socket_keepalive = arguments.get("socket_keepalive", None) + self.enable_retry_client = arguments.get("enable_retry_client", False) + self.retry_attempts = arguments.get("retry_attempts", None) + self.retry_delay = arguments.get("retry_delay", None) + self.retry_for = arguments.get("retry_for", None) + self.do_not_retry_for = arguments.get("do_not_retry_for", None) + self.hashclient_retry_attempts = arguments.get( + "hashclient_retry_attempts", 2 + ) + self.hashclient_retry_timeout = arguments.get( + "hashclient_retry_timeout", 1 + ) + self.dead_timeout = arguments.get("hashclient_dead_timeout", 60) + if ( + self.retry_delay is not None + or self.retry_attempts is not None + or self.retry_for is not None + or self.do_not_retry_for is not None + ) and not self.enable_retry_client: + warnings.warn( + "enable_retry_client is not set; retry options " + "will be ignored" + ) + + def _imports(self): + global pymemcache + import pymemcache + + def _create_client(self): + _kwargs = { + "serde": self.serde, + "default_noreply": self.default_noreply, + "tls_context": self.tls_context, + "retry_attempts": self.hashclient_retry_attempts, + "retry_timeout": self.hashclient_retry_timeout, + "dead_timeout": self.dead_timeout, + } + if self.socket_keepalive is not None: + _kwargs.update({"socket_keepalive": self.socket_keepalive}) + + client = pymemcache.client.hash.HashClient(self.url, **_kwargs) + if self.enable_retry_client: + return pymemcache.client.retrying.RetryingClient( + client, + attempts=self.retry_attempts, + retry_delay=self.retry_delay, + retry_for=self.retry_for, + do_not_retry_for=self.do_not_retry_for, + ) + + return client diff --git a/libs/common/dogpile/cache/backends/memory.py b/libs/common/dogpile/cache/backends/memory.py index e2083f7f..f09b3029 100644 --- a/libs/common/dogpile/cache/backends/memory.py +++ b/libs/common/dogpile/cache/backends/memory.py @@ -10,8 +10,10 @@ places the value as given into the dictionary. """ -from ..api import CacheBackend, NO_VALUE -from ...util.compat import pickle + +from ..api import CacheBackend +from ..api import DefaultSerialization +from ..api import NO_VALUE class MemoryBackend(CacheBackend): @@ -47,39 +49,21 @@ class MemoryBackend(CacheBackend): """ - pickle_values = False def __init__(self, arguments): self._cache = arguments.pop("cache_dict", {}) def get(self, key): - value = self._cache.get(key, NO_VALUE) - if value is not NO_VALUE and self.pickle_values: - value = pickle.loads(value) - return value + return self._cache.get(key, NO_VALUE) def get_multi(self, keys): - ret = [ - self._cache.get(key, NO_VALUE) - for key in keys] - if self.pickle_values: - ret = [ - pickle.loads(value) - if value is not NO_VALUE else value - for value in ret - ] - return ret + return [self._cache.get(key, NO_VALUE) for key in keys] def set(self, key, value): - if self.pickle_values: - value = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) self._cache[key] = value def set_multi(self, mapping): - pickle_values = self.pickle_values for key, value in mapping.items(): - if pickle_values: - value = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) self._cache[key] = value def delete(self, key): @@ -90,7 +74,7 @@ class MemoryBackend(CacheBackend): self._cache.pop(key, None) -class MemoryPickleBackend(MemoryBackend): +class MemoryPickleBackend(DefaultSerialization, MemoryBackend): """A backend that uses a plain dictionary, but serializes objects on :meth:`.MemoryBackend.set` and deserializes :meth:`.MemoryBackend.get`. @@ -121,4 +105,3 @@ class MemoryPickleBackend(MemoryBackend): .. versionadded:: 0.5.3 """ - pickle_values = True diff --git a/libs/common/dogpile/cache/backends/null.py b/libs/common/dogpile/cache/backends/null.py index 603cca3f..b4ad0fb3 100644 --- a/libs/common/dogpile/cache/backends/null.py +++ b/libs/common/dogpile/cache/backends/null.py @@ -10,10 +10,11 @@ caching for a region that is otherwise used normally. """ -from ..api import CacheBackend, NO_VALUE +from ..api import CacheBackend +from ..api import NO_VALUE -__all__ = ['NullBackend'] +__all__ = ["NullBackend"] class NullLock(object): @@ -23,6 +24,9 @@ class NullLock(object): def release(self): pass + def locked(self): + return False + class NullBackend(CacheBackend): """A "null" backend that effectively disables all cache operations. diff --git a/libs/common/dogpile/cache/backends/redis.py b/libs/common/dogpile/cache/backends/redis.py index d665320a..1a6ba09a 100644 --- a/libs/common/dogpile/cache/backends/redis.py +++ b/libs/common/dogpile/cache/backends/redis.py @@ -7,16 +7,24 @@ Provides backends for talking to `Redis `_. """ from __future__ import absolute_import -from ..api import CacheBackend, NO_VALUE -from ...util.compat import pickle, u -redis = None +import typing +import warnings -__all__ = 'RedisBackend', +from ..api import BytesBackend +from ..api import NO_VALUE + +if typing.TYPE_CHECKING: + import redis +else: + # delayed import + redis = None # noqa F811 + +__all__ = ("RedisBackend", "RedisSentinelBackend") -class RedisBackend(CacheBackend): - """A `Redis `_ backend, using the +class RedisBackend(BytesBackend): + r"""A `Redis `_ backend, using the `redis-py `_ backend. Example configuration:: @@ -30,23 +38,21 @@ class RedisBackend(CacheBackend): 'port': 6379, 'db': 0, 'redis_expiration_time': 60*60*2, # 2 hours - 'distributed_lock': True + 'distributed_lock': True, + 'thread_local_lock': False } ) + Arguments accepted in the arguments dictionary: :param url: string. If provided, will override separate host/port/db params. The format is that accepted by ``StrictRedis.from_url()``. - .. versionadded:: 0.4.1 - :param host: string, default is ``localhost``. :param password: string, default is no password. - .. versionadded:: 0.4.1 - :param port: integer, default is ``6379``. :param db: integer, default is ``0``. @@ -56,57 +62,66 @@ class RedisBackend(CacheBackend): cache expiration. By default no expiration is set. :param distributed_lock: boolean, when True, will use a - redis-lock as the dogpile lock. - Use this when multiple - processes will be talking to the same redis instance. - When left at False, dogpile will coordinate on a regular - threading mutex. + redis-lock as the dogpile lock. Use this when multiple processes will be + talking to the same redis instance. When left at False, dogpile will + coordinate on a regular threading mutex. :param lock_timeout: integer, number of seconds after acquiring a lock that Redis should expire it. This argument is only valid when ``distributed_lock`` is ``True``. - .. versionadded:: 0.5.0 - :param socket_timeout: float, seconds for socket timeout. Default is None (no timeout). - .. versionadded:: 0.5.4 - :param lock_sleep: integer, number of seconds to sleep when failed to acquire a lock. This argument is only valid when ``distributed_lock`` is ``True``. - .. versionadded:: 0.5.0 - :param connection_pool: ``redis.ConnectionPool`` object. If provided, this object supersedes other connection arguments passed to the ``redis.StrictRedis`` instance, including url and/or host as well as socket_timeout, and will be passed to ``redis.StrictRedis`` as the source of connectivity. - .. versionadded:: 0.5.4 + :param thread_local_lock: bool, whether a thread-local Redis lock object + should be used. This is the default, but is not compatible with + asynchronous runners, as they run in a different thread than the one + used to create the lock. + :param connection_kwargs: dict, additional keyword arguments are passed + along to the + ``StrictRedis.from_url()`` method or ``StrictRedis()`` constructor + directly, including parameters like ``ssl``, ``ssl_certfile``, + ``charset``, etc. + + .. versionadded:: 1.1.6 Added ``connection_kwargs`` parameter. """ def __init__(self, arguments): arguments = arguments.copy() self._imports() - self.url = arguments.pop('url', None) - self.host = arguments.pop('host', 'localhost') - self.password = arguments.pop('password', None) - self.port = arguments.pop('port', 6379) - self.db = arguments.pop('db', 0) - self.distributed_lock = arguments.get('distributed_lock', False) - self.socket_timeout = arguments.pop('socket_timeout', None) + self.url = arguments.pop("url", None) + self.host = arguments.pop("host", "localhost") + self.password = arguments.pop("password", None) + self.port = arguments.pop("port", 6379) + self.db = arguments.pop("db", 0) + self.distributed_lock = arguments.pop("distributed_lock", False) + self.socket_timeout = arguments.pop("socket_timeout", None) + self.lock_timeout = arguments.pop("lock_timeout", None) + self.lock_sleep = arguments.pop("lock_sleep", 0.1) + self.thread_local_lock = arguments.pop("thread_local_lock", True) + self.connection_kwargs = arguments.pop("connection_kwargs", {}) - self.lock_timeout = arguments.get('lock_timeout', None) - self.lock_sleep = arguments.get('lock_sleep', 0.1) + if self.distributed_lock and self.thread_local_lock: + warnings.warn( + "The Redis backend thread_local_lock parameter should be " + "set to False when distributed_lock is True" + ) - self.redis_expiration_time = arguments.pop('redis_expiration_time', 0) - self.connection_pool = arguments.get('connection_pool', None) - self.client = self._create_client() + self.redis_expiration_time = arguments.pop("redis_expiration_time", 0) + self.connection_pool = arguments.pop("connection_pool", None) + self._create_client() def _imports(self): # defer imports until backend is used @@ -118,66 +133,207 @@ class RedisBackend(CacheBackend): # the connection pool already has all other connection # options present within, so here we disregard socket_timeout # and others. - return redis.StrictRedis(connection_pool=self.connection_pool) - - args = {} - if self.socket_timeout: - args['socket_timeout'] = self.socket_timeout - - if self.url is not None: - args.update(url=self.url) - return redis.StrictRedis.from_url(**args) - else: - args.update( - host=self.host, password=self.password, - port=self.port, db=self.db + self.writer_client = redis.StrictRedis( + connection_pool=self.connection_pool ) - return redis.StrictRedis(**args) + self.reader_client = self.writer_client + else: + args = {} + args.update(self.connection_kwargs) + if self.socket_timeout: + args["socket_timeout"] = self.socket_timeout + + if self.url is not None: + args.update(url=self.url) + self.writer_client = redis.StrictRedis.from_url(**args) + self.reader_client = self.writer_client + else: + args.update( + host=self.host, + password=self.password, + port=self.port, + db=self.db, + ) + self.writer_client = redis.StrictRedis(**args) + self.reader_client = self.writer_client def get_mutex(self, key): if self.distributed_lock: - return self.client.lock(u('_lock{0}').format(key), - self.lock_timeout, self.lock_sleep) + return _RedisLockWrapper( + self.writer_client.lock( + "_lock{0}".format(key), + timeout=self.lock_timeout, + sleep=self.lock_sleep, + thread_local=self.thread_local_lock, + ) + ) else: return None - def get(self, key): - value = self.client.get(key) + def get_serialized(self, key): + value = self.reader_client.get(key) if value is None: return NO_VALUE - return pickle.loads(value) + return value - def get_multi(self, keys): + def get_serialized_multi(self, keys): if not keys: return [] - values = self.client.mget(keys) - return [ - pickle.loads(v) if v is not None else NO_VALUE - for v in values] + values = self.reader_client.mget(keys) + return [v if v is not None else NO_VALUE for v in values] - def set(self, key, value): + def set_serialized(self, key, value): if self.redis_expiration_time: - self.client.setex(key, self.redis_expiration_time, - pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + self.writer_client.setex(key, self.redis_expiration_time, value) else: - self.client.set(key, pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) - - def set_multi(self, mapping): - mapping = dict( - (k, pickle.dumps(v, pickle.HIGHEST_PROTOCOL)) - for k, v in mapping.items() - ) + self.writer_client.set(key, value) + def set_serialized_multi(self, mapping): if not self.redis_expiration_time: - self.client.mset(mapping) + self.writer_client.mset(mapping) else: - pipe = self.client.pipeline() + pipe = self.writer_client.pipeline() for key, value in mapping.items(): pipe.setex(key, self.redis_expiration_time, value) pipe.execute() def delete(self, key): - self.client.delete(key) + self.writer_client.delete(key) def delete_multi(self, keys): - self.client.delete(*keys) + self.writer_client.delete(*keys) + + +class _RedisLockWrapper: + __slots__ = ("mutex", "__weakref__") + + def __init__(self, mutex: typing.Any): + self.mutex = mutex + + def acquire(self, wait: bool = True) -> typing.Any: + return self.mutex.acquire(blocking=wait) + + def release(self) -> typing.Any: + return self.mutex.release() + + def locked(self) -> bool: + return self.mutex.locked() # type: ignore + + +class RedisSentinelBackend(RedisBackend): + """A `Redis `_ backend, using the + `redis-py `_ backend. + It will use the Sentinel of a Redis cluster. + + .. versionadded:: 1.0.0 + + Example configuration:: + + from dogpile.cache import make_region + + region = make_region().configure( + 'dogpile.cache.redis_sentinel', + arguments = { + 'sentinels': [ + ['redis_sentinel_1', 26379], + ['redis_sentinel_2', 26379] + ], + 'db': 0, + 'redis_expiration_time': 60*60*2, # 2 hours + 'distributed_lock': True, + 'thread_local_lock': False + } + ) + + + Arguments accepted in the arguments dictionary: + + :param db: integer, default is ``0``. + + :param redis_expiration_time: integer, number of seconds after setting + a value that Redis should expire it. This should be larger than dogpile's + cache expiration. By default no expiration is set. + + :param distributed_lock: boolean, when True, will use a + redis-lock as the dogpile lock. Use this when multiple processes will be + talking to the same redis instance. When False, dogpile will + coordinate on a regular threading mutex, Default is True. + + :param lock_timeout: integer, number of seconds after acquiring a lock that + Redis should expire it. This argument is only valid when + ``distributed_lock`` is ``True``. + + :param socket_timeout: float, seconds for socket timeout. + Default is None (no timeout). + + :param sentinels: is a list of sentinel nodes. Each node is represented by + a pair (hostname, port). + Default is None (not in sentinel mode). + + :param service_name: str, the service name. + Default is 'mymaster'. + + :param sentinel_kwargs: is a dictionary of connection arguments used when + connecting to sentinel instances. Any argument that can be passed to + a normal Redis connection can be specified here. + Default is {}. + + :param connection_kwargs: dict, additional keyword arguments are passed + along to the + ``StrictRedis.from_url()`` method or ``StrictRedis()`` constructor + directly, including parameters like ``ssl``, ``ssl_certfile``, + ``charset``, etc. + + :param lock_sleep: integer, number of seconds to sleep when failed to + acquire a lock. This argument is only valid when + ``distributed_lock`` is ``True``. + + :param thread_local_lock: bool, whether a thread-local Redis lock object + should be used. This is the default, but is not compatible with + asynchronous runners, as they run in a different thread than the one + used to create the lock. + + """ + + def __init__(self, arguments): + arguments = arguments.copy() + + self.sentinels = arguments.pop("sentinels", None) + self.service_name = arguments.pop("service_name", "mymaster") + self.sentinel_kwargs = arguments.pop("sentinel_kwargs", {}) + + super().__init__( + arguments={ + "distributed_lock": True, + "thread_local_lock": False, + **arguments, + } + ) + + def _imports(self): + # defer imports until backend is used + global redis + import redis.sentinel # noqa + + def _create_client(self): + sentinel_kwargs = {} + sentinel_kwargs.update(self.sentinel_kwargs) + sentinel_kwargs.setdefault("password", self.password) + + connection_kwargs = {} + connection_kwargs.update(self.connection_kwargs) + connection_kwargs.setdefault("password", self.password) + + if self.db is not None: + connection_kwargs.setdefault("db", self.db) + sentinel_kwargs.setdefault("db", self.db) + if self.socket_timeout is not None: + connection_kwargs.setdefault("socket_timeout", self.socket_timeout) + + sentinel = redis.sentinel.Sentinel( + self.sentinels, + sentinel_kwargs=sentinel_kwargs, + **connection_kwargs, + ) + self.writer_client = sentinel.master_for(self.service_name) + self.reader_client = sentinel.slave_for(self.service_name) diff --git a/libs/common/dogpile/cache/plugins/mako_cache.py b/libs/common/dogpile/cache/plugins/mako_cache.py index 61f4ffaf..b1bf6201 100644 --- a/libs/common/dogpile/cache/plugins/mako_cache.py +++ b/libs/common/dogpile/cache/plugins/mako_cache.py @@ -51,20 +51,22 @@ class MakoPlugin(CacheImpl): def __init__(self, cache): super(MakoPlugin, self).__init__(cache) try: - self.regions = self.cache.template.cache_args['regions'] + self.regions = self.cache.template.cache_args["regions"] except KeyError: raise KeyError( "'cache_regions' argument is required on the " "Mako Lookup or Template object for usage " - "with the dogpile.cache plugin.") + "with the dogpile.cache plugin." + ) def _get_region(self, **kw): try: - region = kw['region'] + region = kw["region"] except KeyError: raise KeyError( "'cache_region' argument must be specified with 'cache=True'" - "within templates for usage with the dogpile.cache plugin.") + "within templates for usage with the dogpile.cache plugin." + ) try: return self.regions[region] except KeyError: @@ -73,8 +75,8 @@ class MakoPlugin(CacheImpl): def get_and_replace(self, key, creation_function, **kw): expiration_time = kw.pop("timeout", None) return self._get_region(**kw).get_or_create( - key, creation_function, - expiration_time=expiration_time) + key, creation_function, expiration_time=expiration_time + ) def get_or_create(self, key, creation_function, **kw): return self.get_and_replace(key, creation_function, **kw) diff --git a/libs/common/dogpile/cache/proxy.py b/libs/common/dogpile/cache/proxy.py index 15c6b574..bf6e296b 100644 --- a/libs/common/dogpile/cache/proxy.py +++ b/libs/common/dogpile/cache/proxy.py @@ -10,7 +10,16 @@ base backend. """ +from typing import Mapping +from typing import Optional +from typing import Sequence + +from .api import BackendFormatted +from .api import BackendSetType from .api import CacheBackend +from .api import CacheMutex +from .api import KeyType +from .api import SerializedReturnType class ProxyBackend(CacheBackend): @@ -55,17 +64,17 @@ class ProxyBackend(CacheBackend): """ - def __init__(self, *args, **kwargs): - self.proxied = None + def __init__(self, *arg, **kw): + pass - def wrap(self, backend): - ''' Take a backend as an argument and setup the self.proxied property. + def wrap(self, backend: CacheBackend) -> "ProxyBackend": + """Take a backend as an argument and setup the self.proxied property. Return an object that be used as a backend by a :class:`.CacheRegion` object. - ''' - assert( - isinstance(backend, CacheBackend) or - isinstance(backend, ProxyBackend)) + """ + assert isinstance(backend, CacheBackend) or isinstance( + backend, ProxyBackend + ) self.proxied = backend return self @@ -73,23 +82,37 @@ class ProxyBackend(CacheBackend): # Delegate any functions that are not already overridden to # the proxies backend # - def get(self, key): + def get(self, key: KeyType) -> BackendFormatted: return self.proxied.get(key) - def set(self, key, value): + def set(self, key: KeyType, value: BackendSetType) -> None: self.proxied.set(key, value) - def delete(self, key): + def delete(self, key: KeyType) -> None: self.proxied.delete(key) - def get_multi(self, keys): + def get_multi(self, keys: Sequence[KeyType]) -> Sequence[BackendFormatted]: return self.proxied.get_multi(keys) - def set_multi(self, mapping): + def set_multi(self, mapping: Mapping[KeyType, BackendSetType]) -> None: self.proxied.set_multi(mapping) - def delete_multi(self, keys): + def delete_multi(self, keys: Sequence[KeyType]) -> None: self.proxied.delete_multi(keys) - def get_mutex(self, key): + def get_mutex(self, key: KeyType) -> Optional[CacheMutex]: return self.proxied.get_mutex(key) + + def get_serialized(self, key: KeyType) -> SerializedReturnType: + return self.proxied.get_serialized(key) + + def get_serialized_multi( + self, keys: Sequence[KeyType] + ) -> Sequence[SerializedReturnType]: + return self.proxied.get_serialized_multi(keys) + + def set_serialized(self, key: KeyType, value: bytes) -> None: + self.proxied.set_serialized(key, value) + + def set_serialized_multi(self, mapping: Mapping[KeyType, bytes]) -> None: + self.proxied.set_serialized_multi(mapping) diff --git a/libs/common/dogpile/cache/region.py b/libs/common/dogpile/cache/region.py index 261a8db4..ef0dbc49 100644 --- a/libs/common/dogpile/cache/region.py +++ b/libs/common/dogpile/cache/region.py @@ -1,32 +1,75 @@ from __future__ import with_statement -from .. import Lock, NeedRegenerationException -from ..util import NameRegistry -from . import exception -from ..util import PluginLoader, memoized_property, coerce_string_conf -from .util import function_key_generator, function_multi_key_generator -from .api import NO_VALUE, CachedValue -from .proxy import ProxyBackend -from ..util import compat -import time + +import contextlib import datetime +from functools import partial +from functools import wraps +import json +import logging from numbers import Number -from functools import wraps, partial import threading +import time +from typing import Any +from typing import Callable +from typing import cast +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import Union + from decorator import decorate -_backend_loader = PluginLoader("dogpile.cache") -register_backend = _backend_loader.register -from . import backends # noqa +from . import exception +from .api import BackendArguments +from .api import BackendFormatted +from .api import CachedValue +from .api import CacheMutex +from .api import CacheReturnType +from .api import KeyType +from .api import MetaDataType +from .api import NO_VALUE +from .api import SerializedReturnType +from .api import Serializer +from .api import ValuePayload +from .backends import _backend_loader +from .backends import register_backend # noqa +from .proxy import ProxyBackend +from .util import function_key_generator +from .util import function_multi_key_generator +from .util import repr_obj +from .. import Lock +from .. import NeedRegenerationException +from ..util import coerce_string_conf +from ..util import memoized_property +from ..util import NameRegistry +from ..util import PluginLoader -value_version = 1 +value_version = 2 """An integer placed in the :class:`.CachedValue` so that new versions of dogpile.cache can detect cached values from a previous, backwards-incompatible version. """ +log = logging.getLogger(__name__) -class RegionInvalidationStrategy(object): + +AsyncCreator = Callable[ + ["CacheRegion", KeyType, Callable[[], ValuePayload], CacheMutex], None +] + +ExpirationTimeCallable = Callable[[], float] + +ToStr = Callable[[Any], str] + +FunctionKeyGenerator = Callable[..., Callable[..., KeyType]] + +FunctionMultiKeyGenerator = Callable[..., Callable[..., Sequence[KeyType]]] + + +class RegionInvalidationStrategy: """Region invalidation strategy interface Implement this interface and pass implementation instance @@ -74,7 +117,7 @@ class RegionInvalidationStrategy(object): region = CacheRegion() - region = region.configure(region_invalidator=CustomInvalidationStrategy()) + region = region.configure(region_invalidator=CustomInvalidationStrategy()) # noqa Invalidation strategies that wish to have access to the :class:`.CacheRegion` itself should construct the invalidator given the @@ -98,7 +141,7 @@ class RegionInvalidationStrategy(object): """ - def invalidate(self, hard=True): + def invalidate(self, hard: bool = True) -> None: """Region invalidation. :class:`.CacheRegion` propagated call. @@ -110,7 +153,7 @@ class RegionInvalidationStrategy(object): raise NotImplementedError() - def is_hard_invalidated(self, timestamp): + def is_hard_invalidated(self, timestamp: float) -> bool: """Check timestamp to determine if it was hard invalidated. :return: Boolean. True if ``timestamp`` is older than @@ -121,7 +164,7 @@ class RegionInvalidationStrategy(object): raise NotImplementedError() - def is_soft_invalidated(self, timestamp): + def is_soft_invalidated(self, timestamp: float) -> bool: """Check timestamp to determine if it was soft invalidated. :return: Boolean. True if ``timestamp`` is older than @@ -132,7 +175,7 @@ class RegionInvalidationStrategy(object): raise NotImplementedError() - def is_invalidated(self, timestamp): + def is_invalidated(self, timestamp: float) -> bool: """Check timestamp to determine if it was invalidated. :return: Boolean. True if ``timestamp`` is older than @@ -142,7 +185,7 @@ class RegionInvalidationStrategy(object): raise NotImplementedError() - def was_soft_invalidated(self): + def was_soft_invalidated(self) -> bool: """Indicate the region was invalidated in soft mode. :return: Boolean. True if region was invalidated in soft mode. @@ -151,7 +194,7 @@ class RegionInvalidationStrategy(object): raise NotImplementedError() - def was_hard_invalidated(self): + def was_hard_invalidated(self) -> bool: """Indicate the region was invalidated in hard mode. :return: Boolean. True if region was invalidated in hard mode. @@ -162,33 +205,31 @@ class RegionInvalidationStrategy(object): class DefaultInvalidationStrategy(RegionInvalidationStrategy): - def __init__(self): self._is_hard_invalidated = None self._invalidated = None - def invalidate(self, hard=True): + def invalidate(self, hard: bool = True) -> None: self._is_hard_invalidated = bool(hard) self._invalidated = time.time() - def is_invalidated(self, timestamp): - return (self._invalidated is not None and - timestamp < self._invalidated) + def is_invalidated(self, timestamp: float) -> bool: + return self._invalidated is not None and timestamp < self._invalidated - def was_hard_invalidated(self): + def was_hard_invalidated(self) -> bool: return self._is_hard_invalidated is True - def is_hard_invalidated(self, timestamp): + def is_hard_invalidated(self, timestamp: float) -> bool: return self.was_hard_invalidated() and self.is_invalidated(timestamp) - def was_soft_invalidated(self): + def was_soft_invalidated(self) -> bool: return self._is_hard_invalidated is False - def is_soft_invalidated(self, timestamp): + def is_soft_invalidated(self, timestamp: float) -> bool: return self.was_soft_invalidated() and self.is_invalidated(timestamp) -class CacheRegion(object): +class CacheRegion: r"""A front end to a particular cache backend. :param name: Optional, a string name for the region. @@ -274,6 +315,21 @@ class CacheRegion(object): to convert non-string or Unicode keys to bytestrings, which is needed when using a backend such as bsddb or dbm under Python 2.x in conjunction with Unicode keys. + + :param serializer: function which will be applied to all values before + passing to the backend. Defaults to ``None``, in which case the + serializer recommended by the backend will be used. Typical + serializers include ``pickle.dumps`` and ``json.dumps``. + + .. versionadded:: 1.1.0 + + :param deserializer: function which will be applied to all values returned + by the backend. Defaults to ``None``, in which case the + deserializer recommended by the backend will be used. Typical + deserializers include ``pickle.dumps`` and ``json.dumps``. + + .. versionadded:: 1.1.0 + :param async_creation_runner: A callable that, when specified, will be passed to and called by dogpile.lock when there is a stale value present in the cache. It will be passed the @@ -328,31 +384,38 @@ class CacheRegion(object): """ def __init__( - self, - name=None, - function_key_generator=function_key_generator, - function_multi_key_generator=function_multi_key_generator, - key_mangler=None, - async_creation_runner=None, + self, + name: Optional[str] = None, + function_key_generator: FunctionKeyGenerator = function_key_generator, + function_multi_key_generator: FunctionMultiKeyGenerator = function_multi_key_generator, # noqa E501 + key_mangler: Optional[Callable[[KeyType], KeyType]] = None, + serializer: Optional[Callable[[ValuePayload], bytes]] = None, + deserializer: Optional[Callable[[bytes], ValuePayload]] = None, + async_creation_runner: Optional[AsyncCreator] = None, ): """Construct a new :class:`.CacheRegion`.""" self.name = name self.function_key_generator = function_key_generator self.function_multi_key_generator = function_multi_key_generator self.key_mangler = self._user_defined_key_mangler = key_mangler + self.serializer = self._user_defined_serializer = serializer + self.deserializer = self._user_defined_deserializer = deserializer self.async_creation_runner = async_creation_runner - self.region_invalidator = DefaultInvalidationStrategy() + self.region_invalidator: RegionInvalidationStrategy = ( + DefaultInvalidationStrategy() + ) def configure( - self, backend, - expiration_time=None, - arguments=None, - _config_argument_dict=None, - _config_prefix=None, - wrap=None, - replace_existing_backend=False, - region_invalidator=None - ): + self, + backend: str, + expiration_time: Optional[Union[float, datetime.timedelta]] = None, + arguments: Optional[BackendArguments] = None, + _config_argument_dict: Optional[Mapping[str, Any]] = None, + _config_prefix: Optional[str] = None, + wrap: Sequence[Union[ProxyBackend, Type[ProxyBackend]]] = (), + replace_existing_backend: bool = False, + region_invalidator: Optional[RegionInvalidationStrategy] = None, + ) -> "CacheRegion": """Configure a :class:`.CacheRegion`. The :class:`.CacheRegion` itself @@ -403,44 +466,53 @@ class CacheRegion(object): .. versionadded:: 0.6.2 - """ + """ if "backend" in self.__dict__ and not replace_existing_backend: raise exception.RegionAlreadyConfigured( "This region is already " "configured with backend: %s. " "Specify replace_existing_backend=True to replace." - % self.backend) + % self.backend + ) try: backend_cls = _backend_loader.load(backend) except PluginLoader.NotFound: raise exception.PluginNotFound( - "Couldn't find cache plugin to load: %s" % backend) + "Couldn't find cache plugin to load: %s" % backend + ) if _config_argument_dict: self.backend = backend_cls.from_config_dict( - _config_argument_dict, - _config_prefix + _config_argument_dict, _config_prefix ) else: self.backend = backend_cls(arguments or {}) + self.expiration_time: Union[float, None] + if not expiration_time or isinstance(expiration_time, Number): - self.expiration_time = expiration_time + self.expiration_time = cast(Union[None, float], expiration_time) elif isinstance(expiration_time, datetime.timedelta): - self.expiration_time = int( - compat.timedelta_total_seconds(expiration_time)) + self.expiration_time = int(expiration_time.total_seconds()) else: raise exception.ValidationError( - 'expiration_time is not a number or timedelta.') + "expiration_time is not a number or timedelta." + ) if not self._user_defined_key_mangler: self.key_mangler = self.backend.key_mangler + if not self._user_defined_serializer: + self.serializer = self.backend.serializer + + if not self._user_defined_deserializer: + self.deserializer = self.backend.deserializer + self._lock_registry = NameRegistry(self._create_mutex) - if getattr(wrap, '__iter__', False): + if getattr(wrap, "__iter__", False): for wrapper in reversed(wrap): self.wrap(wrapper) @@ -449,26 +521,30 @@ class CacheRegion(object): return self - def wrap(self, proxy): - ''' Takes a ProxyBackend instance or class and wraps the - attached backend. ''' + def wrap(self, proxy: Union[ProxyBackend, Type[ProxyBackend]]) -> None: + """Takes a ProxyBackend instance or class and wraps the + attached backend.""" # if we were passed a type rather than an instance then # initialize it. - if type(proxy) == type: - proxy = proxy() + if isinstance(proxy, type): + proxy_instance = proxy() + else: + proxy_instance = proxy - if not issubclass(type(proxy), ProxyBackend): - raise TypeError("Type %s is not a valid ProxyBackend" - % type(proxy)) + if not isinstance(proxy_instance, ProxyBackend): + raise TypeError( + "%r is not a valid ProxyBackend" % (proxy_instance,) + ) - self.backend = proxy.wrap(self.backend) + self.backend = proxy_instance.wrap(self.backend) def _mutex(self, key): return self._lock_registry.get(key) - class _LockWrapper(object): + class _LockWrapper(CacheMutex): """weakref-capable wrapper for threading.Lock""" + def __init__(self): self.lock = threading.Lock() @@ -478,6 +554,9 @@ class CacheRegion(object): def release(self): self.lock.release() + def locked(self): + return self.lock.locked() + def _create_mutex(self, key): mutex = self.backend.get_mutex(key) if mutex is not None: @@ -500,7 +579,7 @@ class CacheRegion(object): """ if self._actual_backend is None: _backend = self.backend - while hasattr(_backend, 'proxied'): + while hasattr(_backend, "proxied"): _backend = _backend.proxied self._actual_backend = _backend return self._actual_backend @@ -583,19 +662,21 @@ class CacheRegion(object): return self.configure( config_dict["%sbackend" % prefix], expiration_time=config_dict.get( - "%sexpiration_time" % prefix, None), + "%sexpiration_time" % prefix, None + ), _config_argument_dict=config_dict, _config_prefix="%sarguments." % prefix, - wrap=config_dict.get( - "%swrap" % prefix, None), + wrap=config_dict.get("%swrap" % prefix, None), replace_existing_backend=config_dict.get( - "%sreplace_existing_backend" % prefix, False), + "%sreplace_existing_backend" % prefix, False + ), ) @memoized_property def backend(self): raise exception.RegionNotConfigured( - "No backend is configured on this region.") + "No backend is configured on this region." + ) @property def is_configured(self): @@ -605,7 +686,7 @@ class CacheRegion(object): .. versionadded:: 0.5.1 """ - return 'backend' in self.__dict__ + return "backend" in self.__dict__ def get(self, key, expiration_time=None, ignore_expiration=False): """Return a value from the cache, based on the given key. @@ -650,6 +731,13 @@ class CacheRegion(object): which will supersede that configured on the :class:`.CacheRegion` itself. + .. note:: The :paramref:`.CacheRegion.get.expiration_time` + argument is **not persisted in the cache** and is relevant + only to **this specific cache retrieval operation**, relative to + the creation time stored with the existing cached value. + Subsequent calls to :meth:`.CacheRegion.get` are **not** affected + by this value. + .. versionadded:: 0.3.0 :param ignore_expiration: if ``True``, the value is returned @@ -659,13 +747,25 @@ class CacheRegion(object): .. versionadded:: 0.3.0 + .. seealso:: + + :meth:`.CacheRegion.get_multi` + + :meth:`.CacheRegion.get_or_create` + + :meth:`.CacheRegion.set` + + :meth:`.CacheRegion.delete` + + """ if self.key_mangler: key = self.key_mangler(key) - value = self.backend.get(key) - value = self._unexpired_value_fn( - expiration_time, ignore_expiration)(value) + value = self._get_from_backend(key) + value = self._unexpired_value_fn(expiration_time, ignore_expiration)( + value + ) return value.payload @@ -681,11 +781,14 @@ class CacheRegion(object): def value_fn(value): if value is NO_VALUE: return value - elif expiration_time is not None and \ - current_time - value.metadata["ct"] > expiration_time: + elif ( + expiration_time is not None + and current_time - value.metadata["ct"] > expiration_time + ): return NO_VALUE elif self.region_invalidator.is_invalidated( - value.metadata["ct"]): + value.metadata["ct"] + ): return NO_VALUE else: return value @@ -727,25 +830,64 @@ class CacheRegion(object): if not keys: return [] - if self.key_mangler: - keys = list(map(lambda key: self.key_mangler(key), keys)) + if self.key_mangler is not None: + keys = [self.key_mangler(key) for key in keys] - backend_values = self.backend.get_multi(keys) + backend_values = self._get_multi_from_backend(keys) _unexpired_value_fn = self._unexpired_value_fn( - expiration_time, ignore_expiration) + expiration_time, ignore_expiration + ) return [ value.payload if value is not NO_VALUE else value - for value in - ( - _unexpired_value_fn(value) for value in - backend_values + for value in ( + _unexpired_value_fn(value) for value in backend_values ) ] + @contextlib.contextmanager + def _log_time(self, keys): + start_time = time.time() + yield + seconds = time.time() - start_time + log.debug( + "Cache value generated in %(seconds).3f seconds for key(s): " + "%(keys)r", + {"seconds": seconds, "keys": repr_obj(keys)}, + ) + + def _is_cache_miss(self, value, orig_key): + if value is NO_VALUE: + log.debug("No value present for key: %r", orig_key) + elif value.metadata["v"] != value_version: + log.debug("Dogpile version update for key: %r", orig_key) + elif self.region_invalidator.is_hard_invalidated(value.metadata["ct"]): + log.debug("Hard invalidation detected for key: %r", orig_key) + else: + return False + + return True + + def key_is_locked(self, key: KeyType) -> bool: + """Return True if a particular cache key is currently being generated + within the dogpile lock. + + .. versionadded:: 1.1.2 + + """ + mutex = self._mutex(key) + locked: bool = mutex.locked() + return locked + def get_or_create( - self, key, creator, expiration_time=None, should_cache_fn=None, - creator_args=None): + self, + key: KeyType, + creator: Callable[..., ValuePayload], + expiration_time: Optional[float] = None, + should_cache_fn: Optional[Callable[[ValuePayload], bool]] = None, + creator_args: Optional[Tuple[Any, Mapping[str, Any]]] = None, + ) -> ValuePayload: + """Return a cached value based on the given key. If the value does not exist or is considered to be expired @@ -786,10 +928,17 @@ class CacheRegion(object): .. versionadded:: 0.7.0 - :param expiration_time: optional expiration time which will overide + :param expiration_time: optional expiration time which will override the expiration time already configured on this :class:`.CacheRegion` if not None. To set no expiration, use the value -1. + .. note:: The :paramref:`.CacheRegion.get_or_create.expiration_time` + argument is **not persisted in the cache** and is relevant + only to **this specific cache retrieval operation**, relative to + the creation time stored with the existing cached value. + Subsequent calls to :meth:`.CacheRegion.get_or_create` are **not** + affected by this value. + :param should_cache_fn: optional callable function which will receive the value returned by the "creator", and will then return True or False, indicating if the value should actually be cached or not. If @@ -811,11 +960,13 @@ class CacheRegion(object): .. seealso:: + :meth:`.CacheRegion.get` + :meth:`.CacheRegion.cache_on_arguments` - applies :meth:`.get_or_create` to any function using a decorator. :meth:`.CacheRegion.get_or_create_multi` - multiple key/value - version + version """ orig_key = key @@ -823,64 +974,87 @@ class CacheRegion(object): key = self.key_mangler(key) def get_value(): - value = self.backend.get(key) - if (value is NO_VALUE or value.metadata['v'] != value_version or - self.region_invalidator.is_hard_invalidated( - value.metadata["ct"])): + value = self._get_from_backend(key) + if self._is_cache_miss(value, orig_key): raise NeedRegenerationException() - ct = value.metadata["ct"] + + ct = cast(CachedValue, value).metadata["ct"] if self.region_invalidator.is_soft_invalidated(ct): - ct = time.time() - expiration_time - .0001 + if expiration_time is None: + raise exception.DogpileCacheException( + "Non-None expiration time required " + "for soft invalidation" + ) + ct = time.time() - expiration_time - 0.0001 return value.payload, ct def gen_value(): - if creator_args: - created_value = creator(*creator_args[0], **creator_args[1]) - else: - created_value = creator() + with self._log_time(orig_key): + if creator_args: + created_value = creator( + *creator_args[0], **creator_args[1] + ) + else: + created_value = creator() value = self._value(created_value) - if not should_cache_fn or \ - should_cache_fn(created_value): - self.backend.set(key, value) + if ( + expiration_time is None + and self.region_invalidator.was_soft_invalidated() + ): + raise exception.DogpileCacheException( + "Non-None expiration time required " + "for soft invalidation" + ) + + if not should_cache_fn or should_cache_fn(created_value): + self._set_cached_value_to_backend(key, value) return value.payload, value.metadata["ct"] if expiration_time is None: expiration_time = self.expiration_time - if (expiration_time is None and - self.region_invalidator.was_soft_invalidated()): - raise exception.DogpileCacheException( - "Non-None expiration time required " - "for soft invalidation") - if expiration_time == -1: expiration_time = None + async_creator: Optional[Callable[[CacheMutex], AsyncCreator]] if self.async_creation_runner: + acr = self.async_creation_runner + def async_creator(mutex): if creator_args: + + ca = creator_args + @wraps(creator) def go(): - return creator(*creator_args[0], **creator_args[1]) + return creator(*ca[0], **ca[1]) + else: go = creator - return self.async_creation_runner(self, orig_key, go, mutex) + return acr(self, orig_key, go, mutex) + else: async_creator = None with Lock( - self._mutex(key), - gen_value, - get_value, - expiration_time, - async_creator) as value: + self._mutex(key), + gen_value, + get_value, + expiration_time, + async_creator, + ) as value: return value def get_or_create_multi( - self, keys, creator, expiration_time=None, should_cache_fn=None): + self, + keys: Sequence[KeyType], + creator: Callable[[], ValuePayload], + expiration_time: Optional[float] = None, + should_cache_fn: Optional[Callable[[ValuePayload], bool]] = None, + ) -> Sequence[ValuePayload]: """Return a sequence of cached values based on a sequence of keys. The behavior for generation of values based on keys corresponds @@ -906,7 +1080,7 @@ class CacheRegion(object): :param creator: function which accepts a sequence of keys and returns a sequence of new values. - :param expiration_time: optional expiration time which will overide + :param expiration_time: optional expiration time which will override the expiration time already configured on this :class:`.CacheRegion` if not None. To set no expiration, use the value -1. @@ -929,40 +1103,35 @@ class CacheRegion(object): def get_value(key): value = values.get(key, NO_VALUE) - if (value is NO_VALUE or value.metadata['v'] != value_version or - self.region_invalidator.is_hard_invalidated( - value.metadata['ct'])): + if self._is_cache_miss(value, orig_key): # dogpile.core understands a 0 here as # "the value is not available", e.g. # _has_value() will return False. return value.payload, 0 else: - ct = value.metadata["ct"] + ct = cast(CachedValue, value).metadata["ct"] if self.region_invalidator.is_soft_invalidated(ct): - ct = time.time() - expiration_time - .0001 + if expiration_time is None: + raise exception.DogpileCacheException( + "Non-None expiration time required " + "for soft invalidation" + ) + ct = time.time() - expiration_time - 0.0001 return value.payload, ct - def gen_value(): + def gen_value() -> ValuePayload: raise NotImplementedError() - def async_creator(key, mutex): + def async_creator(mutexes, key, mutex): mutexes[key] = mutex if expiration_time is None: expiration_time = self.expiration_time - if (expiration_time is None and - self.region_invalidator.was_soft_invalidated()): - raise exception.DogpileCacheException( - "Non-None expiration time required " - "for soft invalidation") - if expiration_time == -1: expiration_time = None - mutexes = {} - sorted_unique_keys = sorted(set(keys)) if self.key_mangler: @@ -972,15 +1141,21 @@ class CacheRegion(object): orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys)) - values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys))) + values = dict( + zip(mangled_keys, self._get_multi_from_backend(mangled_keys)) + ) + + mutexes: Mapping[KeyType, Any] = {} for orig_key, mangled_key in orig_to_mangled.items(): with Lock( - self._mutex(mangled_key), - gen_value, - lambda: get_value(mangled_key), - expiration_time, - async_creator=lambda mutex: async_creator(orig_key, mutex) + self._mutex(mangled_key), + gen_value, + lambda: get_value(mangled_key), + expiration_time, + async_creator=lambda mutex: async_creator( + mutexes, orig_key, mutex + ), ): pass try: @@ -988,24 +1163,35 @@ class CacheRegion(object): # sort the keys, the idea is to prevent deadlocks. # though haven't been able to simulate one anyway. keys_to_get = sorted(mutexes) - new_values = creator(*keys_to_get) - values_w_created = dict( - (orig_to_mangled[k], self._value(v)) + with self._log_time(keys_to_get): + new_values = creator(*keys_to_get) + + values_w_created = { + orig_to_mangled[k]: self._value(v) for k, v in zip(keys_to_get, new_values) - ) + } - if not should_cache_fn: - self.backend.set_multi(values_w_created) - else: - values_to_cache = dict( - (k, v) - for k, v in values_w_created.items() - if should_cache_fn(v[0]) + if ( + expiration_time is None + and self.region_invalidator.was_soft_invalidated() + ): + raise exception.DogpileCacheException( + "Non-None expiration time required " + "for soft invalidation" ) - if values_to_cache: - self.backend.set_multi(values_to_cache) + if not should_cache_fn: + self._set_multi_cached_value_to_backend(values_w_created) + + else: + self._set_multi_cached_value_to_backend( + { + k: v + for k, v in values_w_created.items() + if should_cache_fn(v.payload) + } + ) values.update(values_w_created) return [values[orig_to_mangled[k]].payload for k in keys] @@ -1013,40 +1199,162 @@ class CacheRegion(object): for mutex in mutexes.values(): mutex.release() - def _value(self, value): + def _value( + self, value: Any, metadata: Optional[MetaDataType] = None + ) -> CachedValue: """Return a :class:`.CachedValue` given a value.""" - return CachedValue( - value, - { - "ct": time.time(), - "v": value_version - }) - def set(self, key, value): + if metadata is None: + metadata = self._gen_metadata() + return CachedValue(value, metadata) + + def _parse_serialized_from_backend( + self, value: SerializedReturnType + ) -> CacheReturnType: + if value in (None, NO_VALUE): + return NO_VALUE + + assert self.deserializer + byte_value = cast(bytes, value) + + bytes_metadata, _, bytes_payload = byte_value.partition(b"|") + metadata = json.loads(bytes_metadata) + payload = self.deserializer(bytes_payload) + return CachedValue(payload, metadata) + + def _serialize_cached_value_elements( + self, payload: ValuePayload, metadata: MetaDataType + ) -> bytes: + serializer = cast(Serializer, self.serializer) + + return b"%b|%b" % ( + json.dumps(metadata).encode("ascii"), + serializer(payload), + ) + + def _serialized_payload( + self, payload: ValuePayload, metadata: Optional[MetaDataType] = None + ) -> BackendFormatted: + """Return a backend formatted representation of a value. + + If a serializer is in use then this will return a string representation + with the value formatted by the serializer. + + """ + if metadata is None: + metadata = self._gen_metadata() + + return self._serialize_cached_value_elements(payload, metadata) + + def _serialized_cached_value(self, value: CachedValue) -> BackendFormatted: + """Return a backend formatted representation of a :class:`.CachedValue`. + + If a serializer is in use then this will return a string representation + with the value formatted by the serializer. + + """ + + assert self.serializer + return self._serialize_cached_value_elements( + value.payload, value.metadata + ) + + def _get_from_backend(self, key: KeyType) -> CacheReturnType: + if self.deserializer: + return self._parse_serialized_from_backend( + self.backend.get_serialized(key) + ) + else: + return cast(CacheReturnType, self.backend.get(key)) + + def _get_multi_from_backend( + self, keys: Sequence[KeyType] + ) -> Sequence[CacheReturnType]: + if self.deserializer: + return [ + self._parse_serialized_from_backend(v) + for v in self.backend.get_serialized_multi(keys) + ] + else: + return cast( + Sequence[CacheReturnType], self.backend.get_multi(keys) + ) + + def _set_cached_value_to_backend( + self, key: KeyType, value: CachedValue + ) -> None: + if self.serializer: + self.backend.set_serialized( + key, self._serialized_cached_value(value) + ) + else: + self.backend.set(key, value) + + def _set_multi_cached_value_to_backend( + self, mapping: Mapping[KeyType, CachedValue] + ) -> None: + if not mapping: + return + + if self.serializer: + self.backend.set_serialized_multi( + { + k: self._serialized_cached_value(v) + for k, v in mapping.items() + } + ) + else: + self.backend.set_multi(mapping) + + def _gen_metadata(self) -> MetaDataType: + return {"ct": time.time(), "v": value_version} + + def set(self, key: KeyType, value: ValuePayload) -> None: """Place a new value in the cache under the given key.""" if self.key_mangler: key = self.key_mangler(key) - self.backend.set(key, self._value(value)) - def set_multi(self, mapping): - """Place new values in the cache under the given keys. + if self.serializer: + self.backend.set_serialized(key, self._serialized_payload(value)) + else: + self.backend.set(key, self._value(value)) - .. versionadded:: 0.5.0 - - """ + def set_multi(self, mapping: Mapping[KeyType, ValuePayload]) -> None: + """Place new values in the cache under the given keys.""" if not mapping: return - if self.key_mangler: - mapping = dict(( - self.key_mangler(k), self._value(v)) - for k, v in mapping.items()) - else: - mapping = dict((k, self._value(v)) for k, v in mapping.items()) - self.backend.set_multi(mapping) + metadata = self._gen_metadata() - def delete(self, key): + if self.serializer: + if self.key_mangler: + mapping = { + self.key_mangler(k): self._serialized_payload( + v, metadata=metadata + ) + for k, v in mapping.items() + } + else: + mapping = { + k: self._serialized_payload(v, metadata=metadata) + for k, v in mapping.items() + } + self.backend.set_serialized_multi(mapping) + else: + if self.key_mangler: + mapping = { + self.key_mangler(k): self._value(v, metadata=metadata) + for k, v in mapping.items() + } + else: + mapping = { + k: self._value(v, metadata=metadata) + for k, v in mapping.items() + } + self.backend.set_multi(mapping) + + def delete(self, key: KeyType) -> None: """Remove a value from the cache. This operation is idempotent (can be called multiple times, or on a @@ -1058,7 +1366,7 @@ class CacheRegion(object): self.backend.delete(key) - def delete_multi(self, keys): + def delete_multi(self, keys: Sequence[KeyType]) -> None: """Remove multiple values from the cache. This operation is idempotent (can be called multiple times, or on a @@ -1069,16 +1377,19 @@ class CacheRegion(object): """ if self.key_mangler: - keys = list(map(lambda key: self.key_mangler(key), keys)) + km = self.key_mangler + keys = [km(key) for key in keys] self.backend.delete_multi(keys) def cache_on_arguments( - self, namespace=None, - expiration_time=None, - should_cache_fn=None, - to_str=compat.string_type, - function_key_generator=None): + self, + namespace: Optional[str] = None, + expiration_time: Union[float, ExpirationTimeCallable, None] = None, + should_cache_fn: Optional[Callable[[ValuePayload], bool]] = None, + to_str: Callable[[Any], str] = str, + function_key_generator: Optional[FunctionKeyGenerator] = None, + ) -> Callable[[Callable[..., ValuePayload]], Callable[..., ValuePayload]]: """A function decorator that will cache the return value of the function using a key derived from the function itself and its arguments. @@ -1171,7 +1482,7 @@ class CacheRegion(object): (with caveats) for use with instance or class methods. Given the example:: - class MyClass(object): + class MyClass: @region.cache_on_arguments(namespace="foo") def one(self, a, b): return a + b @@ -1185,12 +1496,12 @@ class CacheRegion(object): name within the same module, as can occur when decorating instance or class methods as below:: - class MyClass(object): + class MyClass: @region.cache_on_arguments(namespace='MC') def somemethod(self, x, y): "" - class MyOtherClass(object): + class MyOtherClass: @region.cache_on_arguments(namespace='MOC') def somemethod(self, x, y): "" @@ -1228,14 +1539,8 @@ class CacheRegion(object): end of the day, week or time period" and "cache until a certain date or time passes". - .. versionchanged:: 0.5.0 - ``expiration_time`` may be passed as a callable to - :meth:`.CacheRegion.cache_on_arguments`. - :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create`. - .. versionadded:: 0.4.3 - :param to_str: callable, will be called on each function argument in order to convert to a string. Defaults to ``str()``. If the function accepts non-ascii unicode arguments on Python 2.x, the @@ -1243,14 +1548,10 @@ class CacheRegion(object): produce unicode cache keys which may require key mangling before reaching the cache. - .. versionadded:: 0.5.0 - :param function_key_generator: a function that will produce a "cache key". This function will supersede the one configured on the :class:`.CacheRegion` itself. - .. versionadded:: 0.5.5 - .. seealso:: :meth:`.CacheRegion.cache_multi_on_arguments` @@ -1258,27 +1559,35 @@ class CacheRegion(object): :meth:`.CacheRegion.get_or_create` """ - expiration_time_is_callable = compat.callable(expiration_time) + expiration_time_is_callable = callable(expiration_time) if function_key_generator is None: - function_key_generator = self.function_key_generator + _function_key_generator = self.function_key_generator + else: + _function_key_generator = function_key_generator def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): key = key_generator(*arg, **kw) - timeout = expiration_time() if expiration_time_is_callable \ - else expiration_time - return self.get_or_create(key, user_func, timeout, - should_cache_fn, (arg, kw)) + timeout: Optional[float] = ( + cast(ExpirationTimeCallable, expiration_time)() + if expiration_time_is_callable + else cast(Optional[float], expiration_time) + ) + return self.get_or_create( + key, user_func, timeout, should_cache_fn, (arg, kw) + ) def cache_decorator(user_func): - if to_str is compat.string_type: + if to_str is cast(Callable[[Any], str], str): # backwards compatible - key_generator = function_key_generator(namespace, user_func) + key_generator = _function_key_generator( + namespace, user_func + ) # type: ignore else: - key_generator = function_key_generator( - namespace, user_func, - to_str=to_str) + key_generator = _function_key_generator( + namespace, user_func, to_str + ) def refresh(*arg, **kw): """ @@ -1309,16 +1618,28 @@ class CacheRegion(object): # Use `decorate` to preserve the signature of :param:`user_func`. - return decorate(user_func, partial( - get_or_create_for_user_func, key_generator)) + return decorate( + user_func, partial(get_or_create_for_user_func, key_generator) + ) return cache_decorator def cache_multi_on_arguments( - self, namespace=None, expiration_time=None, - should_cache_fn=None, - asdict=False, to_str=compat.string_type, - function_multi_key_generator=None): + self, + namespace: Optional[str] = None, + expiration_time: Union[float, ExpirationTimeCallable, None] = None, + should_cache_fn: Optional[Callable[[ValuePayload], bool]] = None, + asdict: bool = False, + to_str: ToStr = str, + function_multi_key_generator: Optional[ + FunctionMultiKeyGenerator + ] = None, + ) -> Callable[ + [Callable[..., Sequence[ValuePayload]]], + Callable[ + ..., Union[Sequence[ValuePayload], Mapping[KeyType, ValuePayload]] + ], + ]: """A function decorator that will cache multiple return values from the function using a sequence of keys derived from the function itself and the arguments passed to it. @@ -1435,12 +1756,19 @@ class CacheRegion(object): :meth:`.CacheRegion.get_or_create_multi` """ - expiration_time_is_callable = compat.callable(expiration_time) + expiration_time_is_callable = callable(expiration_time) if function_multi_key_generator is None: - function_multi_key_generator = self.function_multi_key_generator + _function_multi_key_generator = self.function_multi_key_generator + else: + _function_multi_key_generator = function_multi_key_generator - def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): + def get_or_create_for_user_func( + key_generator: Callable[..., Sequence[KeyType]], + user_func: Callable[..., Sequence[ValuePayload]], + *arg: Any, + **kw: Any, + ) -> Union[Sequence[ValuePayload], Mapping[KeyType, ValuePayload]]: cache_keys = arg keys = key_generator(*arg, **kw) key_lookup = dict(zip(keys, cache_keys)) @@ -1449,15 +1777,23 @@ class CacheRegion(object): def creator(*keys_to_create): return user_func(*[key_lookup[k] for k in keys_to_create]) - timeout = expiration_time() if expiration_time_is_callable \ - else expiration_time + timeout: Optional[float] = ( + cast(ExpirationTimeCallable, expiration_time)() + if expiration_time_is_callable + else cast(Optional[float], expiration_time) + ) + + result: Union[ + Sequence[ValuePayload], Mapping[KeyType, ValuePayload] + ] if asdict: + def dict_create(*keys): d_values = creator(*keys) return [ - d_values.get(key_lookup[k], NO_VALUE) - for k in keys] + d_values.get(key_lookup[k], NO_VALUE) for k in keys + ] def wrap_cache_fn(value): if value is NO_VALUE: @@ -1468,21 +1804,24 @@ class CacheRegion(object): return should_cache_fn(value) result = self.get_or_create_multi( - keys, dict_create, timeout, wrap_cache_fn) + keys, dict_create, timeout, wrap_cache_fn + ) result = dict( - (k, v) for k, v in zip(cache_keys, result) - if v is not NO_VALUE) + (k, v) + for k, v in zip(cache_keys, result) + if v is not NO_VALUE + ) else: result = self.get_or_create_multi( - keys, creator, timeout, - should_cache_fn) + keys, creator, timeout, should_cache_fn + ) return result def cache_decorator(user_func): - key_generator = function_multi_key_generator( - namespace, user_func, - to_str=to_str) + key_generator = _function_multi_key_generator( + namespace, user_func, to_str=to_str + ) def invalidate(*arg): keys = key_generator(*arg) @@ -1491,10 +1830,11 @@ class CacheRegion(object): def set_(mapping): keys = list(mapping) gen_keys = key_generator(*keys) - self.set_multi(dict( - (gen_key, mapping[key]) - for gen_key, key - in zip(gen_keys, keys)) + self.set_multi( + dict( + (gen_key, mapping[key]) + for gen_key, key in zip(gen_keys, keys) + ) ) def get(*arg): @@ -1505,14 +1845,10 @@ class CacheRegion(object): keys = key_generator(*arg) values = user_func(*arg) if asdict: - self.set_multi( - dict(zip(keys, [values[a] for a in arg])) - ) + self.set_multi(dict(zip(keys, [values[a] for a in arg]))) return values else: - self.set_multi( - dict(zip(keys, values)) - ) + self.set_multi(dict(zip(keys, values))) return values user_func.set = set_ @@ -1522,14 +1858,14 @@ class CacheRegion(object): # Use `decorate` to preserve the signature of :param:`user_func`. - return decorate(user_func, partial(get_or_create_for_user_func, key_generator)) + return decorate( + user_func, partial(get_or_create_for_user_func, key_generator) + ) return cache_decorator - - -def make_region(*arg, **kw): +def make_region(*arg: Any, **kw: Any) -> CacheRegion: """Instantiate a new :class:`.CacheRegion`. Currently, :func:`.make_region` is a passthrough diff --git a/libs/common/dogpile/cache/util.py b/libs/common/dogpile/cache/util.py index 16bcd1c9..6bf6cefe 100644 --- a/libs/common/dogpile/cache/util.py +++ b/libs/common/dogpile/cache/util.py @@ -1,9 +1,10 @@ from hashlib import sha1 + from ..util import compat from ..util import langhelpers -def function_key_generator(namespace, fn, to_str=compat.string_type): +def function_key_generator(namespace, fn, to_str=str): """Return a function that generates a string key, based on a given function as well as arguments to the returned function itself. @@ -23,47 +24,51 @@ def function_key_generator(namespace, fn, to_str=compat.string_type): """ if namespace is None: - namespace = '%s:%s' % (fn.__module__, fn.__name__) + namespace = "%s:%s" % (fn.__module__, fn.__name__) else: - namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) + namespace = "%s:%s|%s" % (fn.__module__, fn.__name__, namespace) args = compat.inspect_getargspec(fn) - has_self = args[0] and args[0][0] in ('self', 'cls') + has_self = args[0] and args[0][0] in ("self", "cls") def generate_key(*args, **kw): if kw: raise ValueError( "dogpile.cache's default key creation " - "function does not accept keyword arguments.") + "function does not accept keyword arguments." + ) if has_self: args = args[1:] return namespace + "|" + " ".join(map(to_str, args)) + return generate_key -def function_multi_key_generator(namespace, fn, to_str=compat.string_type): +def function_multi_key_generator(namespace, fn, to_str=str): if namespace is None: - namespace = '%s:%s' % (fn.__module__, fn.__name__) + namespace = "%s:%s" % (fn.__module__, fn.__name__) else: - namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) + namespace = "%s:%s|%s" % (fn.__module__, fn.__name__, namespace) args = compat.inspect_getargspec(fn) - has_self = args[0] and args[0][0] in ('self', 'cls') + has_self = args[0] and args[0][0] in ("self", "cls") def generate_keys(*args, **kw): if kw: raise ValueError( "dogpile.cache's default key creation " - "function does not accept keyword arguments.") + "function does not accept keyword arguments." + ) if has_self: args = args[1:] return [namespace + "|" + key for key in map(to_str, args)] + return generate_keys -def kwarg_function_key_generator(namespace, fn, to_str=compat.string_type): +def kwarg_function_key_generator(namespace, fn, to_str=str): """Return a function that generates a string key, based on a given function as well as arguments to the returned function itself. @@ -83,9 +88,9 @@ def kwarg_function_key_generator(namespace, fn, to_str=compat.string_type): """ if namespace is None: - namespace = '%s:%s' % (fn.__module__, fn.__name__) + namespace = "%s:%s" % (fn.__module__, fn.__name__) else: - namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) + namespace = "%s:%s|%s" % (fn.__module__, fn.__name__, namespace) argspec = compat.inspect_getargspec(fn) default_list = list(argspec.defaults or []) @@ -94,32 +99,41 @@ def kwarg_function_key_generator(namespace, fn, to_str=compat.string_type): # enumerate() default_list.reverse() # use idx*-1 to create the correct right-lookup index. - args_with_defaults = dict((argspec.args[(idx*-1)], default) - for idx, default in enumerate(default_list, 1)) - if argspec.args and argspec.args[0] in ('self', 'cls'): + args_with_defaults = dict( + (argspec.args[(idx * -1)], default) + for idx, default in enumerate(default_list, 1) + ) + if argspec.args and argspec.args[0] in ("self", "cls"): arg_index_start = 1 else: arg_index_start = 0 def generate_key(*args, **kwargs): as_kwargs = dict( - [(argspec.args[idx], arg) - for idx, arg in enumerate(args[arg_index_start:], - arg_index_start)]) + [ + (argspec.args[idx], arg) + for idx, arg in enumerate( + args[arg_index_start:], arg_index_start + ) + ] + ) as_kwargs.update(kwargs) for arg, val in args_with_defaults.items(): if arg not in as_kwargs: as_kwargs[arg] = val - argument_values = [as_kwargs[key] - for key in sorted(as_kwargs.keys())] - return namespace + '|' + " ".join(map(to_str, argument_values)) + argument_values = [as_kwargs[key] for key in sorted(as_kwargs.keys())] + return namespace + "|" + " ".join(map(to_str, argument_values)) + return generate_key def sha1_mangle_key(key): """a SHA1 key mangler.""" + if isinstance(key, str): + key = key.encode("utf-8") + return sha1(key).hexdigest() @@ -128,13 +142,16 @@ def length_conditional_mangler(length, mangler): past a certain threshold. """ + def mangle(key): if len(key) >= length: return mangler(key) else: return key + return mangle + # in the 0.6 release these functions were moved to the dogpile.util namespace. # They are linked here to maintain compatibility with older versions. @@ -143,3 +160,30 @@ KeyReentrantMutex = langhelpers.KeyReentrantMutex memoized_property = langhelpers.memoized_property PluginLoader = langhelpers.PluginLoader to_list = langhelpers.to_list + + +class repr_obj: + + __slots__ = ("value", "max_chars") + + def __init__(self, value, max_chars=300): + self.value = value + self.max_chars = max_chars + + def __eq__(self, other): + return other.value == self.value + + def __repr__(self): + rep = repr(self.value) + lenrep = len(rep) + if lenrep > self.max_chars: + segment_length = self.max_chars // 2 + rep = ( + rep[0:segment_length] + + ( + " ... (%d characters truncated) ... " + % (lenrep - self.max_chars) + ) + + rep[-segment_length:] + ) + return rep diff --git a/libs/common/dogpile/core.py b/libs/common/dogpile/core.py index 2bcfaf81..ce147612 100644 --- a/libs/common/dogpile/core.py +++ b/libs/common/dogpile/core.py @@ -8,10 +8,10 @@ dogpile.core installation is present. """ -from .util import nameregistry # noqa -from .util import readwrite_lock # noqa -from .util.readwrite_lock import ReadWriteMutex # noqa -from .util.nameregistry import NameRegistry # noqa +from . import __version__ # noqa from .lock import Lock # noqa from .lock import NeedRegenerationException # noqa -from . import __version__ # noqa +from .util import nameregistry # noqa +from .util import readwrite_lock # noqa +from .util.nameregistry import NameRegistry # noqa +from .util.readwrite_lock import ReadWriteMutex # noqa diff --git a/libs/common/dogpile/lock.py b/libs/common/dogpile/lock.py index 2ac22dcf..465cd90d 100644 --- a/libs/common/dogpile/lock.py +++ b/libs/common/dogpile/lock.py @@ -1,5 +1,5 @@ -import time import logging +import time log = logging.getLogger(__name__) @@ -11,10 +11,11 @@ class NeedRegenerationException(Exception): """ + NOT_REGENERATED = object() -class Lock(object): +class Lock: """Dogpile lock class. Provides an interface around an arbitrary mutex @@ -70,8 +71,8 @@ class Lock(object): value is available.""" return not self._has_value(createdtime) or ( - self.expiretime is not None and - time.time() - createdtime > self.expiretime + self.expiretime is not None + and time.time() - createdtime > self.expiretime ) def _has_value(self, createdtime): @@ -109,7 +110,8 @@ class Lock(object): raise Exception( "Generation function should " "have just been called by a concurrent " - "thread.") + "thread." + ) else: return value @@ -122,9 +124,7 @@ class Lock(object): if self._has_value(createdtime): has_value = True if not self.mutex.acquire(False): - log.debug( - "creation function in progress " - "elsewhere, returning") + log.debug("creation function in progress elsewhere, returning") return NOT_REGENERATED else: has_value = False @@ -173,8 +173,7 @@ class Lock(object): # there's no value at all, and we have to create it synchronously log.debug( "Calling creation function for %s value", - "not-yet-present" if not has_value else - "previously expired" + "not-yet-present" if not has_value else "previously expired", ) return self.creator() finally: @@ -185,5 +184,5 @@ class Lock(object): def __enter__(self): return self._enter() - def __exit__(self, type, value, traceback): + def __exit__(self, type_, value, traceback): pass diff --git a/libs/common/dogpile/util/__init__.py b/libs/common/dogpile/util/__init__.py index 91b07520..77d65ff4 100644 --- a/libs/common/dogpile/util/__init__.py +++ b/libs/common/dogpile/util/__init__.py @@ -1,4 +1,7 @@ +from .langhelpers import coerce_string_conf # noqa +from .langhelpers import KeyReentrantMutex # noqa +from .langhelpers import memoized_property # noqa +from .langhelpers import PluginLoader # noqa +from .langhelpers import to_list # noqa from .nameregistry import NameRegistry # noqa from .readwrite_lock import ReadWriteMutex # noqa -from .langhelpers import PluginLoader, memoized_property, \ - coerce_string_conf, to_list, KeyReentrantMutex # noqa diff --git a/libs/common/dogpile/util/compat.py b/libs/common/dogpile/util/compat.py index 198c7627..85e4e85f 100644 --- a/libs/common/dogpile/util/compat.py +++ b/libs/common/dogpile/util/compat.py @@ -1,87 +1,72 @@ -import sys - -py2k = sys.version_info < (3, 0) -py3k = sys.version_info >= (3, 0) -py32 = sys.version_info >= (3, 2) -py27 = sys.version_info >= (2, 7) -jython = sys.platform.startswith('java') -win32 = sys.platform.startswith('win') - -try: - import threading -except ImportError: - import dummy_threading as threading # noqa +import collections +import inspect -if py3k: # pragma: no cover - string_types = str, - text_type = str - string_type = str +FullArgSpec = collections.namedtuple( + "FullArgSpec", + [ + "args", + "varargs", + "varkw", + "defaults", + "kwonlyargs", + "kwonlydefaults", + "annotations", + ], +) - if py32: - callable = callable - else: - def callable(fn): - return hasattr(fn, '__call__') - - def u(s): - return s - - def ue(s): - return s - - import configparser - import io - import _thread as thread -else: - string_types = basestring, - text_type = unicode - string_type = str - - def u(s): - return unicode(s, "utf-8") - - def ue(s): - return unicode(s, "unicode_escape") - - import ConfigParser as configparser # noqa - import StringIO as io # noqa - - callable = callable # noqa - import thread # noqa +ArgSpec = collections.namedtuple( + "ArgSpec", ["args", "varargs", "keywords", "defaults"] +) -if py3k: - import collections - ArgSpec = collections.namedtuple( - "ArgSpec", - ["args", "varargs", "keywords", "defaults"]) +def inspect_getfullargspec(func): + """Fully vendored version of getfullargspec from Python 3.3. - from inspect import getfullargspec as inspect_getfullargspec + This version is more performant than the one which appeared in + later Python 3 versions. - def inspect_getargspec(func): - return ArgSpec( - *inspect_getfullargspec(func)[0:4] - ) -else: - from inspect import getargspec as inspect_getargspec # noqa + """ -if py3k or jython: - import pickle -else: - import cPickle as pickle # noqa + # if a Signature is already present, as is the case with newer + # "decorator" package, defer back to built in + if hasattr(func, "__signature__"): + return inspect.getfullargspec(func) -if py3k: - def read_config_file(config, fileobj): - return config.read_file(fileobj) -else: - def read_config_file(config, fileobj): - return config.readfp(fileobj) + if inspect.ismethod(func): + func = func.__func__ + if not inspect.isfunction(func): + raise TypeError("{!r} is not a Python function".format(func)) + + co = func.__code__ + if not inspect.iscode(co): + raise TypeError("{!r} is not a code object".format(co)) + + nargs = co.co_argcount + names = co.co_varnames + nkwargs = co.co_kwonlyargcount + args = list(names[:nargs]) + kwonlyargs = list(names[nargs : nargs + nkwargs]) + + nargs += nkwargs + varargs = None + if co.co_flags & inspect.CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & inspect.CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + + return FullArgSpec( + args, + varargs, + varkw, + func.__defaults__, + kwonlyargs, + func.__kwdefaults__, + func.__annotations__, + ) -def timedelta_total_seconds(td): - if py27: - return td.total_seconds() - else: - return (td.microseconds + ( - td.seconds + td.days * 24 * 3600) * 1e6) / 1e6 +def inspect_getargspec(func): + return ArgSpec(*inspect_getfullargspec(func)[0:4]) diff --git a/libs/common/dogpile/util/langhelpers.py b/libs/common/dogpile/util/langhelpers.py index 4ff8e3e3..a59b24ef 100644 --- a/libs/common/dogpile/util/langhelpers.py +++ b/libs/common/dogpile/util/langhelpers.py @@ -1,44 +1,54 @@ -import re +import abc import collections -from . import compat +import re +import threading +from typing import MutableMapping +from typing import MutableSet + +import stevedore def coerce_string_conf(d): result = {} for k, v in d.items(): - if not isinstance(v, compat.string_types): + if not isinstance(v, str): result[k] = v continue v = v.strip() - if re.match(r'^[-+]?\d+$', v): + if re.match(r"^[-+]?\d+$", v): result[k] = int(v) - elif re.match(r'^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$', v): + elif re.match(r"^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$", v): result[k] = float(v) - elif v.lower() in ('false', 'true'): - result[k] = v.lower() == 'true' - elif v == 'None': + elif v.lower() in ("false", "true"): + result[k] = v.lower() == "true" + elif v == "None": result[k] = None else: result[k] = v return result -class PluginLoader(object): +class PluginLoader: def __init__(self, group): self.group = group - self.impls = {} + self.impls = {} # loaded plugins + self._mgr = None # lazily defined stevedore manager + self._unloaded = {} # plugins registered but not loaded def load(self, name): + if name in self._unloaded: + self.impls[name] = self._unloaded[name]() + return self.impls[name] if name in self.impls: - return self.impls[name]() + return self.impls[name] else: # pragma NO COVERAGE - import pkg_resources - for impl in pkg_resources.iter_entry_points( - self.group, name): - self.impls[name] = impl.load - return impl.load() - else: + if self._mgr is None: + self._mgr = stevedore.ExtensionManager(self.group) + try: + self.impls[name] = self._mgr[name].plugin + return self.impls[name] + except KeyError: raise self.NotFound( "Can't load plugin %s %s" % (self.group, name) ) @@ -47,14 +57,16 @@ class PluginLoader(object): def load(): mod = __import__(modulepath, fromlist=[objname]) return getattr(mod, objname) - self.impls[name] = load + + self._unloaded[name] = load class NotFound(Exception): """The specified plugin could not be found.""" -class memoized_property(object): +class memoized_property: """A read-only @property that is only evaluated once.""" + def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ @@ -77,9 +89,23 @@ def to_list(x, default=None): return x -class KeyReentrantMutex(object): +class Mutex(abc.ABC): + @abc.abstractmethod + def acquire(self, wait: bool = True) -> bool: + raise NotImplementedError() - def __init__(self, key, mutex, keys): + @abc.abstractmethod + def release(self) -> None: + raise NotImplementedError() + + +class KeyReentrantMutex: + def __init__( + self, + key: str, + mutex: Mutex, + keys: MutableMapping[int, MutableSet[str]], + ): self.key = key self.mutex = mutex self.keys = keys @@ -89,17 +115,19 @@ class KeyReentrantMutex(object): # this collection holds zero or one # thread idents as the key; a set of # keynames held as the value. - keystore = collections.defaultdict(set) + keystore: MutableMapping[ + int, MutableSet[str] + ] = collections.defaultdict(set) def fac(key): return KeyReentrantMutex(key, mutex, keystore) + return fac def acquire(self, wait=True): - current_thread = compat.threading.current_thread().ident + current_thread = threading.get_ident() keys = self.keys.get(current_thread) - if keys is not None and \ - self.key not in keys: + if keys is not None and self.key not in keys: # current lockholder, new key. add it in keys.add(self.key) return True @@ -111,7 +139,7 @@ class KeyReentrantMutex(object): return False def release(self): - current_thread = compat.threading.current_thread().ident + current_thread = threading.get_ident() keys = self.keys.get(current_thread) assert keys is not None, "this thread didn't do the acquire" assert self.key in keys, "No acquire held for key '%s'" % self.key @@ -121,3 +149,10 @@ class KeyReentrantMutex(object): # the thread ident and unlock. del self.keys[current_thread] self.mutex.release() + + def locked(self): + current_thread = threading.get_ident() + keys = self.keys.get(current_thread) + if keys is None: + return False + return self.key in keys diff --git a/libs/common/dogpile/util/nameregistry.py b/libs/common/dogpile/util/nameregistry.py index 7087f7cd..3c4cc022 100644 --- a/libs/common/dogpile/util/nameregistry.py +++ b/libs/common/dogpile/util/nameregistry.py @@ -1,4 +1,7 @@ -from .compat import threading +import threading +from typing import Any +from typing import Callable +from typing import MutableMapping import weakref @@ -37,19 +40,16 @@ class NameRegistry(object): method. """ - _locks = weakref.WeakValueDictionary() + _mutex = threading.RLock() - def __init__(self, creator): - """Create a new :class:`.NameRegistry`. - - - """ - self._values = weakref.WeakValueDictionary() + def __init__(self, creator: Callable[..., Any]): + """Create a new :class:`.NameRegistry`.""" + self._values: MutableMapping[str, Any] = weakref.WeakValueDictionary() self._mutex = threading.RLock() self.creator = creator - def get(self, identifier, *args, **kw): + def get(self, identifier: str, *args: Any, **kw: Any) -> Any: r"""Get and possibly create the value. :param identifier: Hash key for the value. @@ -68,7 +68,7 @@ class NameRegistry(object): except KeyError: return self._sync_get(identifier, *args, **kw) - def _sync_get(self, identifier, *args, **kw): + def _sync_get(self, identifier: str, *args: Any, **kw: Any) -> Any: self._mutex.acquire() try: try: @@ -76,11 +76,13 @@ class NameRegistry(object): return self._values[identifier] else: self._values[identifier] = value = self.creator( - identifier, *args, **kw) + identifier, *args, **kw + ) return value except KeyError: self._values[identifier] = value = self.creator( - identifier, *args, **kw) + identifier, *args, **kw + ) return value finally: self._mutex.release() diff --git a/libs/common/dogpile/util/readwrite_lock.py b/libs/common/dogpile/util/readwrite_lock.py index 9b953edb..3f375103 100644 --- a/libs/common/dogpile/util/readwrite_lock.py +++ b/libs/common/dogpile/util/readwrite_lock.py @@ -1,6 +1,6 @@ -from .compat import threading - import logging +import threading + log = logging.getLogger(__name__) @@ -62,13 +62,15 @@ class ReadWriteMutex(object): # check if we are the last asynchronous reader thread # out the door. if self.async_ == 0: - # yes. so if a sync operation is waiting, notifyAll to wake + # yes. so if a sync operation is waiting, notify_all to wake # it up if self.current_sync_operation is not None: - self.condition.notifyAll() + self.condition.notify_all() elif self.async_ < 0: - raise LockError("Synchronizer error - too many " - "release_read_locks called") + raise LockError( + "Synchronizer error - too many " + "release_read_locks called" + ) log.debug("%s released read lock", self) finally: self.condition.release() @@ -93,7 +95,7 @@ class ReadWriteMutex(object): # establish ourselves as the current sync # this indicates to other read/write operations # that they should wait until this is None again - self.current_sync_operation = threading.currentThread() + self.current_sync_operation = threading.current_thread() # now wait again for asyncs to finish if self.async_ > 0: @@ -115,16 +117,18 @@ class ReadWriteMutex(object): """Release the 'write' lock.""" self.condition.acquire() try: - if self.current_sync_operation is not threading.currentThread(): - raise LockError("Synchronizer error - current thread doesn't " - "have the write lock") + if self.current_sync_operation is not threading.current_thread(): + raise LockError( + "Synchronizer error - current thread doesn't " + "have the write lock" + ) # reset the current sync operation so # another can get it self.current_sync_operation = None # tell everyone to get ready - self.condition.notifyAll() + self.condition.notify_all() log.debug("%s released write lock", self) finally: diff --git a/libs/common/importlib_metadata/__init__.py b/libs/common/importlib_metadata/__init__.py new file mode 100644 index 00000000..eec91953 --- /dev/null +++ b/libs/common/importlib_metadata/__init__.py @@ -0,0 +1,631 @@ +import io +import os +import re +import abc +import csv +import sys +import zipp +import email +import pathlib +import operator +import functools +import itertools +import posixpath +import collections + +from ._compat import ( + NullFinder, + PyPy_repr, + install, +) + +from configparser import ConfigParser +from contextlib import suppress +from importlib import import_module +from importlib.abc import MetaPathFinder +from itertools import starmap + + +__all__ = [ + 'Distribution', + 'DistributionFinder', + 'PackageNotFoundError', + 'distribution', + 'distributions', + 'entry_points', + 'files', + 'metadata', + 'requires', + 'version', +] + + +class PackageNotFoundError(ModuleNotFoundError): + """The package was not found.""" + + def __str__(self): + tmpl = "No package metadata was found for {self.name}" + return tmpl.format(**locals()) + + @property + def name(self): + (name,) = self.args + return name + + +class EntryPoint( + PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group') +): + """An entry point as defined by Python packaging conventions. + + See `the packaging docs on entry points + `_ + for more information. + """ + + pattern = re.compile( + r'(?P[\w.]+)\s*' + r'(:\s*(?P[\w.]+))?\s*' + r'(?P\[.*\])?\s*$' + ) + """ + A regular expression describing the syntax for an entry point, + which might look like: + + - module + - package.module + - package.module:attribute + - package.module:object.attribute + - package.module:attr [extra1, extra2] + + Other combinations are possible as well. + + The expression is lenient about whitespace around the ':', + following the attr, and following any extras. + """ + + def load(self): + """Load the entry point from its definition. If only a module + is indicated by the value, return that module. Otherwise, + return the named object. + """ + match = self.pattern.match(self.value) + module = import_module(match.group('module')) + attrs = filter(None, (match.group('attr') or '').split('.')) + return functools.reduce(getattr, attrs, module) + + @property + def module(self): + match = self.pattern.match(self.value) + return match.group('module') + + @property + def attr(self): + match = self.pattern.match(self.value) + return match.group('attr') + + @property + def extras(self): + match = self.pattern.match(self.value) + return list(re.finditer(r'\w+', match.group('extras') or '')) + + @classmethod + def _from_config(cls, config): + return [ + cls(name, value, group) + for group in config.sections() + for name, value in config.items(group) + ] + + @classmethod + def _from_text(cls, text): + config = ConfigParser(delimiters='=') + # case sensitive: https://stackoverflow.com/q/1611799/812183 + config.optionxform = str + try: + config.read_string(text) + except AttributeError: # pragma: nocover + # Python 2 has no read_string + config.readfp(io.StringIO(text)) + return EntryPoint._from_config(config) + + def __iter__(self): + """ + Supply iter so one may construct dicts of EntryPoints easily. + """ + return iter((self.name, self)) + + def __reduce__(self): + return ( + self.__class__, + (self.name, self.value, self.group), + ) + + +class PackagePath(pathlib.PurePosixPath): + """A reference to a path in a package""" + + def read_text(self, encoding='utf-8'): + with self.locate().open(encoding=encoding) as stream: + return stream.read() + + def read_binary(self): + with self.locate().open('rb') as stream: + return stream.read() + + def locate(self): + """Return a path-like object for this path""" + return self.dist.locate_file(self) + + +class FileHash: + def __init__(self, spec): + self.mode, _, self.value = spec.partition('=') + + def __repr__(self): + return ''.format(self.mode, self.value) + + +class Distribution: + """A Python distribution package.""" + + @abc.abstractmethod + def read_text(self, filename): + """Attempt to load metadata file given by the name. + + :param filename: The name of the file in the distribution info. + :return: The text if found, otherwise None. + """ + + @abc.abstractmethod + def locate_file(self, path): + """ + Given a path to a file in this distribution, return a path + to it. + """ + + @classmethod + def from_name(cls, name): + """Return the Distribution for the given package name. + + :param name: The name of the distribution package to search for. + :return: The Distribution instance (or subclass thereof) for the named + package, if found. + :raises PackageNotFoundError: When the named package's distribution + metadata cannot be found. + """ + for resolver in cls._discover_resolvers(): + dists = resolver(DistributionFinder.Context(name=name)) + dist = next(iter(dists), None) + if dist is not None: + return dist + else: + raise PackageNotFoundError(name) + + @classmethod + def discover(cls, **kwargs): + """Return an iterable of Distribution objects for all packages. + + Pass a ``context`` or pass keyword arguments for constructing + a context. + + :context: A ``DistributionFinder.Context`` object. + :return: Iterable of Distribution objects for all packages. + """ + context = kwargs.pop('context', None) + if context and kwargs: + raise ValueError("cannot accept context and kwargs") + context = context or DistributionFinder.Context(**kwargs) + return itertools.chain.from_iterable( + resolver(context) for resolver in cls._discover_resolvers() + ) + + @staticmethod + def at(path): + """Return a Distribution for the indicated metadata path + + :param path: a string or path-like object + :return: a concrete Distribution instance for the path + """ + return PathDistribution(pathlib.Path(path)) + + @staticmethod + def _discover_resolvers(): + """Search the meta_path for resolvers.""" + declared = ( + getattr(finder, 'find_distributions', None) for finder in sys.meta_path + ) + return filter(None, declared) + + @classmethod + def _local(cls, root='.'): + from pep517 import build, meta + + system = build.compat_system(root) + builder = functools.partial( + meta.build, + source_dir=root, + system=system, + ) + return PathDistribution(zipp.Path(meta.build_as_zip(builder))) + + @property + def metadata(self): + """Return the parsed metadata for this Distribution. + + The returned object will have keys that name the various bits of + metadata. See PEP 566 for details. + """ + text = ( + self.read_text('METADATA') + or self.read_text('PKG-INFO') + # This last clause is here to support old egg-info files. Its + # effect is to just end up using the PathDistribution's self._path + # (which points to the egg-info file) attribute unchanged. + or self.read_text('') + ) + return email.message_from_string(text) + + @property + def version(self): + """Return the 'Version' metadata for the distribution package.""" + return self.metadata['Version'] + + @property + def entry_points(self): + return EntryPoint._from_text(self.read_text('entry_points.txt')) + + @property + def files(self): + """Files in this distribution. + + :return: List of PackagePath for this distribution or None + + Result is `None` if the metadata file that enumerates files + (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is + missing. + Result may be empty if the metadata exists but is empty. + """ + file_lines = self._read_files_distinfo() or self._read_files_egginfo() + + def make_file(name, hash=None, size_str=None): + result = PackagePath(name) + result.hash = FileHash(hash) if hash else None + result.size = int(size_str) if size_str else None + result.dist = self + return result + + return file_lines and list(starmap(make_file, csv.reader(file_lines))) + + def _read_files_distinfo(self): + """ + Read the lines of RECORD + """ + text = self.read_text('RECORD') + return text and text.splitlines() + + def _read_files_egginfo(self): + """ + SOURCES.txt might contain literal commas, so wrap each line + in quotes. + """ + text = self.read_text('SOURCES.txt') + return text and map('"{}"'.format, text.splitlines()) + + @property + def requires(self): + """Generated requirements specified for this Distribution""" + reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() + return reqs and list(reqs) + + def _read_dist_info_reqs(self): + return self.metadata.get_all('Requires-Dist') + + def _read_egg_info_reqs(self): + source = self.read_text('requires.txt') + return source and self._deps_from_requires_text(source) + + @classmethod + def _deps_from_requires_text(cls, source): + section_pairs = cls._read_sections(source.splitlines()) + sections = { + section: list(map(operator.itemgetter('line'), results)) + for section, results in itertools.groupby( + section_pairs, operator.itemgetter('section') + ) + } + return cls._convert_egg_info_reqs_to_simple_reqs(sections) + + @staticmethod + def _read_sections(lines): + section = None + for line in filter(None, lines): + section_match = re.match(r'\[(.*)\]$', line) + if section_match: + section = section_match.group(1) + continue + yield locals() + + @staticmethod + def _convert_egg_info_reqs_to_simple_reqs(sections): + """ + Historically, setuptools would solicit and store 'extra' + requirements, including those with environment markers, + in separate sections. More modern tools expect each + dependency to be defined separately, with any relevant + extras and environment markers attached directly to that + requirement. This method converts the former to the + latter. See _test_deps_from_requires_text for an example. + """ + + def make_condition(name): + return name and 'extra == "{name}"'.format(name=name) + + def parse_condition(section): + section = section or '' + extra, sep, markers = section.partition(':') + if extra and markers: + markers = '({markers})'.format(markers=markers) + conditions = list(filter(None, [markers, make_condition(extra)])) + return '; ' + ' and '.join(conditions) if conditions else '' + + for section, deps in sections.items(): + for dep in deps: + yield dep + parse_condition(section) + + +class DistributionFinder(MetaPathFinder): + """ + A MetaPathFinder capable of discovering installed distributions. + """ + + class Context: + """ + Keyword arguments presented by the caller to + ``distributions()`` or ``Distribution.discover()`` + to narrow the scope of a search for distributions + in all DistributionFinders. + + Each DistributionFinder may expect any parameters + and should attempt to honor the canonical + parameters defined below when appropriate. + """ + + name = None + """ + Specific name for which a distribution finder should match. + A name of ``None`` matches all distributions. + """ + + def __init__(self, **kwargs): + vars(self).update(kwargs) + + @property + def path(self): + """ + The path that a distribution finder should search. + + Typically refers to Python package paths and defaults + to ``sys.path``. + """ + return vars(self).get('path', sys.path) + + @abc.abstractmethod + def find_distributions(self, context=Context()): + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching the ``context``, + a DistributionFinder.Context instance. + """ + + +class FastPath: + """ + Micro-optimized class for searching a path for + children. + """ + + def __init__(self, root): + self.root = str(root) + self.base = os.path.basename(self.root).lower() + + def joinpath(self, child): + return pathlib.Path(self.root, child) + + def children(self): + with suppress(Exception): + return os.listdir(self.root or '') + with suppress(Exception): + return self.zip_children() + return [] + + def zip_children(self): + zip_path = zipp.Path(self.root) + names = zip_path.root.namelist() + self.joinpath = zip_path.joinpath + + return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names) + + def search(self, name): + return ( + self.joinpath(child) + for child in self.children() + if name.matches(child, self.base) + ) + + +class Prepared: + """ + A prepared search for metadata on a possibly-named package. + """ + + normalized = None + suffixes = '.dist-info', '.egg-info' + exact_matches = [''][:0] + + def __init__(self, name): + self.name = name + if name is None: + return + self.normalized = self.normalize(name) + self.exact_matches = [self.normalized + suffix for suffix in self.suffixes] + + @staticmethod + def normalize(name): + """ + PEP 503 normalization plus dashes as underscores. + """ + return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + + @staticmethod + def legacy_normalize(name): + """ + Normalize the package name as found in the convention in + older packaging tools versions and specs. + """ + return name.lower().replace('-', '_') + + def matches(self, cand, base): + low = cand.lower() + pre, ext = os.path.splitext(low) + name, sep, rest = pre.partition('-') + return ( + low in self.exact_matches + or ext in self.suffixes + and (not self.normalized or name.replace('.', '_') == self.normalized) + # legacy case: + or self.is_egg(base) + and low == 'egg-info' + ) + + def is_egg(self, base): + normalized = self.legacy_normalize(self.name or '') + prefix = normalized + '-' if normalized else '' + versionless_egg_name = normalized + '.egg' if self.name else '' + return ( + base == versionless_egg_name + or base.startswith(prefix) + and base.endswith('.egg') + ) + + +@install +class MetadataPathFinder(NullFinder, DistributionFinder): + """A degenerate finder for distribution packages on the file system. + + This finder supplies only a find_distributions() method for versions + of Python that do not have a PathFinder find_distributions(). + """ + + def find_distributions(self, context=DistributionFinder.Context()): + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching ``context.name`` + (or all names if ``None`` indicated) along the paths in the list + of directories ``context.path``. + """ + found = self._search_paths(context.name, context.path) + return map(PathDistribution, found) + + @classmethod + def _search_paths(cls, name, paths): + """Find metadata directories in paths heuristically.""" + return itertools.chain.from_iterable( + path.search(Prepared(name)) for path in map(FastPath, paths) + ) + + +class PathDistribution(Distribution): + def __init__(self, path): + """Construct a distribution from a path to the metadata directory. + + :param path: A pathlib.Path or similar object supporting + .joinpath(), __div__, .parent, and .read_text(). + """ + self._path = path + + def read_text(self, filename): + with suppress( + FileNotFoundError, + IsADirectoryError, + KeyError, + NotADirectoryError, + PermissionError, + ): + return self._path.joinpath(filename).read_text(encoding='utf-8') + + read_text.__doc__ = Distribution.read_text.__doc__ + + def locate_file(self, path): + return self._path.parent / path + + +def distribution(distribution_name): + """Get the ``Distribution`` instance for the named package. + + :param distribution_name: The name of the distribution package as a string. + :return: A ``Distribution`` instance (or subclass thereof). + """ + return Distribution.from_name(distribution_name) + + +def distributions(**kwargs): + """Get all ``Distribution`` instances in the current environment. + + :return: An iterable of ``Distribution`` instances. + """ + return Distribution.discover(**kwargs) + + +def metadata(distribution_name): + """Get the metadata for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: An email.Message containing the parsed metadata. + """ + return Distribution.from_name(distribution_name).metadata + + +def version(distribution_name): + """Get the version string for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: The version string for the package as defined in the package's + "Version" metadata key. + """ + return distribution(distribution_name).version + + +def entry_points(): + """Return EntryPoint objects for all installed packages. + + :return: EntryPoint objects for all installed packages. + """ + eps = itertools.chain.from_iterable(dist.entry_points for dist in distributions()) + by_group = operator.attrgetter('group') + ordered = sorted(eps, key=by_group) + grouped = itertools.groupby(ordered, by_group) + return {group: tuple(eps) for group, eps in grouped} + + +def files(distribution_name): + """Return a list of files for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: List of files composing the distribution. + """ + return distribution(distribution_name).files + + +def requires(distribution_name): + """ + Return a list of requirements for the named package. + + :return: An iterator of requirements, suitable for + packaging.requirement.Requirement. + """ + return distribution(distribution_name).requires diff --git a/libs/common/importlib_metadata/_compat.py b/libs/common/importlib_metadata/_compat.py new file mode 100644 index 00000000..c1362d53 --- /dev/null +++ b/libs/common/importlib_metadata/_compat.py @@ -0,0 +1,75 @@ +import sys + + +__all__ = ['install', 'NullFinder', 'PyPy_repr'] + + +def install(cls): + """ + Class decorator for installation on sys.meta_path. + + Adds the backport DistributionFinder to sys.meta_path and + attempts to disable the finder functionality of the stdlib + DistributionFinder. + """ + sys.meta_path.append(cls()) + disable_stdlib_finder() + return cls + + +def disable_stdlib_finder(): + """ + Give the backport primacy for discovering path-based distributions + by monkey-patching the stdlib O_O. + + See #91 for more background for rationale on this sketchy + behavior. + """ + + def matches(finder): + return getattr( + finder, '__module__', None + ) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions') + + for finder in filter(matches, sys.meta_path): # pragma: nocover + del finder.find_distributions + + +class NullFinder: + """ + A "Finder" (aka "MetaClassFinder") that never finds any modules, + but may find distributions. + """ + + @staticmethod + def find_spec(*args, **kwargs): + return None + + # In Python 2, the import system requires finders + # to have a find_module() method, but this usage + # is deprecated in Python 3 in favor of find_spec(). + # For the purposes of this finder (i.e. being present + # on sys.meta_path but having no other import + # system functionality), the two methods are identical. + find_module = find_spec + + +class PyPy_repr: + """ + Override repr for EntryPoint objects on PyPy to avoid __iter__ access. + Ref #97, #102. + """ + + affected = hasattr(sys, 'pypy_version_info') + + def __compat_repr__(self): # pragma: nocover + def make_param(name): + value = getattr(self, name) + return '{name}={value!r}'.format(**locals()) + + params = ', '.join(map(make_param, self._fields)) + return 'EntryPoint({params})'.format(**locals()) + + if affected: # pragma: nocover + __repr__ = __compat_repr__ + del affected diff --git a/libs/common/pbr/build.py b/libs/common/pbr/build.py new file mode 100644 index 00000000..a5355e17 --- /dev/null +++ b/libs/common/pbr/build.py @@ -0,0 +1,61 @@ +# Copyright 2021 Monty Taylor +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""pep-517 support + +Add:: + + [build-system] + requires = ["pbr>=5.7.0", "setuptools>=36.6.0", "wheel"] + build-backend = "pbr.build" + +to pyproject.toml to use this +""" + +from setuptools import build_meta + +__all__ = [ + 'get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', +] + + +def get_requires_for_build_wheel(config_settings=None): + return build_meta.get_requires_for_build_wheel(config_settings) + + +def get_requires_for_build_sdist(config_settings=None): + return build_meta.get_requires_for_build_sdist(config_settings) + + +def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): + return build_meta.prepare_metadata_for_build_wheel( + metadata_directory, config_settings) + + +def build_wheel( + wheel_directory, + config_settings=None, + metadata_directory=None, +): + return build_meta.build_wheel( + wheel_directory, config_settings, metadata_directory, + ) + + +def build_sdist(sdist_directory, config_settings=None): + return build_meta.build_sdist(sdist_directory, config_settings) diff --git a/libs/common/pbr/builddoc.py b/libs/common/pbr/builddoc.py index f5c66ce0..276eec67 100644 --- a/libs/common/pbr/builddoc.py +++ b/libs/common/pbr/builddoc.py @@ -132,11 +132,11 @@ class LocalBuildDoc(setup_command.BuildDoc): autoindex.write(" %s.rst\n" % module) def _sphinx_tree(self): - source_dir = self._get_source_dir() - cmd = ['-H', 'Modules', '-o', source_dir, '.'] - if apidoc_use_padding: - cmd.insert(0, 'apidoc') - apidoc.main(cmd + self.autodoc_tree_excludes) + source_dir = self._get_source_dir() + cmd = ['-H', 'Modules', '-o', source_dir, '.'] + if apidoc_use_padding: + cmd.insert(0, 'apidoc') + apidoc.main(cmd + self.autodoc_tree_excludes) def _sphinx_run(self): if not self.verbose: diff --git a/libs/common/pbr/cmd/main.py b/libs/common/pbr/cmd/main.py index 29cd61d7..162304f7 100644 --- a/libs/common/pbr/cmd/main.py +++ b/libs/common/pbr/cmd/main.py @@ -40,8 +40,11 @@ def get_sha(args): def get_info(args): - print("{name}\t{version}\t{released}\t{sha}".format( - **_get_info(args.name))) + if args.short: + print("{version}".format(**_get_info(args.name))) + else: + print("{name}\t{version}\t{released}\t{sha}".format( + **_get_info(args.name))) def _get_info(name): @@ -86,7 +89,9 @@ def main(): version=str(pbr.version.VersionInfo('pbr'))) subparsers = parser.add_subparsers( - title='commands', description='valid commands', help='additional help') + title='commands', description='valid commands', help='additional help', + dest='cmd') + subparsers.required = True cmd_sha = subparsers.add_parser('sha', help='print sha of package') cmd_sha.set_defaults(func=get_sha) @@ -96,6 +101,8 @@ def main(): 'info', help='print version info for package') cmd_info.set_defaults(func=get_info) cmd_info.add_argument('name', help='package to print info of') + cmd_info.add_argument('-s', '--short', action="store_true", + help='only display package version') cmd_freeze = subparsers.add_parser( 'freeze', help='print version info for all installed packages') diff --git a/libs/common/pbr/core.py b/libs/common/pbr/core.py index 645a2ef1..a801737a 100644 --- a/libs/common/pbr/core.py +++ b/libs/common/pbr/core.py @@ -61,6 +61,11 @@ else: integer_types = (int, long) # noqa +# We use this canary to detect whether the module has already been called, +# in order to avoid recursion +in_use = False + + def pbr(dist, attr, value): """Implements the actual pbr setup() keyword. @@ -81,6 +86,16 @@ def pbr(dist, attr, value): not work well with distributions that do use a `Distribution` subclass. """ + # Distribution.finalize_options() is what calls this method. That means + # there is potential for recursion here. Recursion seems to be an issue + # particularly when using PEP517 build-system configs without + # setup_requires in setup.py. We can avoid the recursion by setting + # this canary so we don't repeat ourselves. + global in_use + if in_use: + return + in_use = True + if not value: return if isinstance(value, string_type): diff --git a/libs/common/pbr/git.py b/libs/common/pbr/git.py index 6e18adae..f1d7c501 100644 --- a/libs/common/pbr/git.py +++ b/libs/common/pbr/git.py @@ -156,9 +156,9 @@ def _clean_changelog_message(msg): * Escapes '`' which is interpreted as a literal """ - msg = msg.replace('*', '\*') - msg = msg.replace('_', '\_') - msg = msg.replace('`', '\`') + msg = msg.replace('*', r'\*') + msg = msg.replace('_', r'\_') + msg = msg.replace('`', r'\`') return msg @@ -223,6 +223,11 @@ def _iter_log_inner(git_dir): presentation logic to the output - making it suitable for different uses. + .. caution:: this function risk to return a tag that doesn't exist really + inside the git objects list due to replacement made + to tag name to also list pre-release suffix. + Compliant with the SemVer specification (e.g 1.2.3-rc1) + :return: An iterator of (hash, tags_set, 1st_line) tuples. """ log.info('[pbr] Generating ChangeLog') @@ -248,7 +253,7 @@ def _iter_log_inner(git_dir): for tag_string in refname.split("refs/tags/")[1:]: # git tag does not allow : or " " in tag names, so we split # on ", " which is the separator between elements - candidate = tag_string.split(", ")[0] + candidate = tag_string.split(", ")[0].replace("-", ".") if _is_valid_version(candidate): tags.add(candidate) @@ -271,13 +276,14 @@ def write_git_changelog(git_dir=None, dest_dir=os.path.curdir, changelog = _iter_changelog(changelog) if not changelog: return + new_changelog = os.path.join(dest_dir, 'ChangeLog') - # If there's already a ChangeLog and it's not writable, just use it - if (os.path.exists(new_changelog) - and not os.access(new_changelog, os.W_OK)): + if os.path.exists(new_changelog) and not os.access(new_changelog, os.W_OK): + # If there's already a ChangeLog and it's not writable, just use it log.info('[pbr] ChangeLog not written (file already' ' exists and it is not writeable)') return + log.info('[pbr] Writing ChangeLog') with io.open(new_changelog, "w", encoding="utf-8") as changelog_file: for release, content in changelog: @@ -292,13 +298,14 @@ def generate_authors(git_dir=None, dest_dir='.', option_dict=dict()): 'SKIP_GENERATE_AUTHORS') if should_skip: return + start = time.time() old_authors = os.path.join(dest_dir, 'AUTHORS.in') new_authors = os.path.join(dest_dir, 'AUTHORS') - # If there's already an AUTHORS file and it's not writable, just use it - if (os.path.exists(new_authors) - and not os.access(new_authors, os.W_OK)): + if os.path.exists(new_authors) and not os.access(new_authors, os.W_OK): + # If there's already an AUTHORS file and it's not writable, just use it return + log.info('[pbr] Generating AUTHORS') ignore_emails = '((jenkins|zuul)@review|infra@lists|jenkins@openstack)' if git_dir is None: diff --git a/libs/common/pbr/hooks/files.py b/libs/common/pbr/hooks/files.py index 48bf9e31..c44af7c4 100644 --- a/libs/common/pbr/hooks/files.py +++ b/libs/common/pbr/hooks/files.py @@ -14,6 +14,7 @@ # under the License. import os +import shlex import sys from pbr import find_package @@ -35,6 +36,21 @@ def get_man_section(section): return os.path.join(get_manpath(), 'man%s' % section) +def unquote_path(path): + # unquote the full path, e.g: "'a/full/path'" becomes "a/full/path", also + # strip the quotes off individual path components because os.walk cannot + # handle paths like: "'i like spaces'/'another dir'", so we will pass it + # "i like spaces/another dir" instead. + + if os.name == 'nt': + # shlex cannot handle paths that contain backslashes, treating those + # as escape characters. + path = path.replace("\\", "/") + return "".join(shlex.split(path)).replace("/", "\\") + + return "".join(shlex.split(path)) + + class FilesConfig(base.BaseConfig): section = 'files' @@ -57,21 +73,28 @@ class FilesConfig(base.BaseConfig): target = target.strip() if not target.endswith(os.path.sep): target += os.path.sep - for (dirpath, dirnames, fnames) in os.walk(source_prefix): - finished.append( - "%s = " % dirpath.replace(source_prefix, target)) + unquoted_prefix = unquote_path(source_prefix) + unquoted_target = unquote_path(target) + for (dirpath, dirnames, fnames) in os.walk(unquoted_prefix): + # As source_prefix is always matched, using replace with a + # a limit of one is always going to replace the path prefix + # and not accidentally replace some text in the middle of + # the path + new_prefix = dirpath.replace(unquoted_prefix, + unquoted_target, 1) + finished.append("'%s' = " % new_prefix) finished.extend( - [" %s" % os.path.join(dirpath, f) for f in fnames]) + [" '%s'" % os.path.join(dirpath, f) for f in fnames]) else: finished.append(line) self.data_files = "\n".join(finished) def add_man_path(self, man_path): - self.data_files = "%s\n%s =" % (self.data_files, man_path) + self.data_files = "%s\n'%s' =" % (self.data_files, man_path) def add_man_page(self, man_page): - self.data_files = "%s\n %s" % (self.data_files, man_page) + self.data_files = "%s\n '%s'" % (self.data_files, man_page) def get_man_sections(self): man_sections = dict() diff --git a/libs/common/pbr/options.py b/libs/common/pbr/options.py index 105b200e..2313cc4a 100644 --- a/libs/common/pbr/options.py +++ b/libs/common/pbr/options.py @@ -48,6 +48,6 @@ TRUE_VALUES = ('true', '1', 'yes') def get_boolean_option(option_dict, option_name, env_name): - return ((option_name in option_dict - and option_dict[option_name][1].lower() in TRUE_VALUES) or + return ((option_name in option_dict and + option_dict[option_name][1].lower() in TRUE_VALUES) or str(os.getenv(env_name)).lower() in TRUE_VALUES) diff --git a/libs/common/pbr/packaging.py b/libs/common/pbr/packaging.py index 77a4b226..474d0130 100644 --- a/libs/common/pbr/packaging.py +++ b/libs/common/pbr/packaging.py @@ -22,6 +22,16 @@ from __future__ import unicode_literals from distutils.command import install as du_install from distutils import log + +# (hberaud) do not use six here to import urlparse +# to keep this module free from external dependencies +# to avoid cross dependencies errors on minimal system +# free from dependencies. +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + import email import email.errors import os @@ -98,19 +108,31 @@ def get_reqs_from_files(requirements_files): return [] +def egg_fragment(match): + return re.sub(r'(?P[\w.-]+)-' + r'(?P' + r'(?P' + r'(?P0|[1-9][0-9]*)\.' + r'(?P0|[1-9][0-9]*)\.' + r'(?P0|[1-9][0-9]*)){1}' + r'(?P(?:\-' + r'(?P(?:(?=[0]{1}[0-9A-Za-z-]{0})(?:[0]{1})|' + r'(?=[1-9]{1}[0-9]*[A-Za-z]{0})(?:[0-9]+)|' + r'(?=[0-9]*[A-Za-z-]+[0-9A-Za-z-]*)(?:[0-9A-Za-z-]+)){1}' + r'(?:\.(?=[0]{1}[0-9A-Za-z-]{0})(?:[0]{1})|' + r'\.(?=[1-9]{1}[0-9]*[A-Za-z]{0})(?:[0-9]+)|' + r'\.(?=[0-9]*[A-Za-z-]+[0-9A-Za-z-]*)' + r'(?:[0-9A-Za-z-]+))*){1}){0,1}(?:\+' + r'(?P(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))){0,1}))', + r'\g>=\g', + match.groups()[-1]) + + def parse_requirements(requirements_files=None, strip_markers=False): if requirements_files is None: requirements_files = get_requirements_files() - def egg_fragment(match): - # take a versioned egg fragment and return a - # versioned package requirement e.g. - # nova-1.2.3 becomes nova>=1.2.3 - return re.sub(r'([\w.]+)-([\w.-]+)', - r'\1>=\2', - match.groups()[-1]) - requirements = [] for line in get_reqs_from_files(requirements_files): # Ignore comments @@ -118,7 +140,8 @@ def parse_requirements(requirements_files=None, strip_markers=False): continue # Ignore index URL lines - if re.match(r'^\s*(-i|--index-url|--extra-index-url).*', line): + if re.match(r'^\s*(-i|--index-url|--extra-index-url|--find-links).*', + line): continue # Handle nested requirements files such as: @@ -140,16 +163,19 @@ def parse_requirements(requirements_files=None, strip_markers=False): # -e git://github.com/openstack/nova/master#egg=nova # -e git://github.com/openstack/nova/master#egg=nova-1.2.3 # -e git+https://foo.com/zipball#egg=bar&subdirectory=baz - if re.match(r'\s*-e\s+', line): - line = re.sub(r'\s*-e\s+.*#egg=([^&]+).*$', egg_fragment, line) - # such as: # http://github.com/openstack/nova/zipball/master#egg=nova # http://github.com/openstack/nova/zipball/master#egg=nova-1.2.3 # git+https://foo.com/zipball#egg=bar&subdirectory=baz - elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line): - line = re.sub(r'\s*(https?|git(\+(https|ssh))?):.*#egg=([^&]+).*$', - egg_fragment, line) + # git+[ssh]://github.com/openstack/nova/zipball/master#egg=nova-1.2.3 + # hg+[ssh]://github.com/openstack/nova/zipball/master#egg=nova-1.2.3 + # svn+[proto]://github.com/openstack/nova/zipball/master#egg=nova-1.2.3 # -f lines are for index locations, and don't get used here + if re.match(r'\s*-e\s+', line): + extract = re.match(r'\s*-e\s+(.*)$', line) + line = extract.group(1) + egg = urlparse(line) + if egg.scheme: + line = re.sub(r'egg=([^&]+).*$', egg_fragment, egg.fragment) elif re.match(r'\s*-f\s+', line): line = None reason = 'Index Location' @@ -183,7 +209,7 @@ def parse_dependency_links(requirements_files=None): if re.match(r'\s*-[ef]\s+', line): dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) # lines that are only urls can go in unmolested - elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line): + elif re.match(r'^\s*(https?|git(\+(https|ssh))?|svn|hg)\S*:', line): dependency_links.append(line) return dependency_links @@ -302,6 +328,7 @@ except ImportError: def have_nose(): return _have_nose + _wsgi_text = """#PBR Generated from %(group)r import threading @@ -404,9 +431,13 @@ def generate_script(group, entry_point, header, template): def override_get_script_args( - dist, executable=os.path.normpath(sys.executable), is_wininst=False): + dist, executable=os.path.normpath(sys.executable)): """Override entrypoints console_script.""" - header = easy_install.get_script_header("", executable, is_wininst) + # get_script_header() is deprecated since Setuptools 12.0 + try: + header = easy_install.ScriptWriter.get_header("", executable) + except AttributeError: + header = easy_install.get_script_header("", executable) for group, template in ENTRY_POINTS_MAP.items(): for name, ep in dist.get_entry_map(group).items(): yield (name, generate_script(group, ep, header, template)) @@ -428,8 +459,12 @@ class LocalInstallScripts(install_scripts.install_scripts): """Intercepts console scripts entry_points.""" command_name = 'install_scripts' - def _make_wsgi_scripts_only(self, dist, executable, is_wininst): - header = easy_install.get_script_header("", executable, is_wininst) + def _make_wsgi_scripts_only(self, dist, executable): + # get_script_header() is deprecated since Setuptools 12.0 + try: + header = easy_install.ScriptWriter.get_header("", executable) + except AttributeError: + header = easy_install.get_script_header("", executable) wsgi_script_template = ENTRY_POINTS_MAP['wsgi_scripts'] for name, ep in dist.get_entry_map('wsgi_scripts').items(): content = generate_script( @@ -455,16 +490,12 @@ class LocalInstallScripts(install_scripts.install_scripts): bs_cmd = self.get_finalized_command('build_scripts') executable = getattr( bs_cmd, 'executable', easy_install.sys_executable) - is_wininst = getattr( - self.get_finalized_command("bdist_wininst"), '_is_running', False - ) - if 'bdist_wheel' in self.distribution.have_run: # We're building a wheel which has no way of generating mod_wsgi # scripts for us. Let's build them. # NOTE(sigmavirus24): This needs to happen here because, as the # comment below indicates, no_ep is True when building a wheel. - self._make_wsgi_scripts_only(dist, executable, is_wininst) + self._make_wsgi_scripts_only(dist, executable) if self.no_ep: # no_ep is True if we're installing into an .egg file or building @@ -478,7 +509,7 @@ class LocalInstallScripts(install_scripts.install_scripts): get_script_args = easy_install.get_script_args executable = '"%s"' % executable - for args in get_script_args(dist, executable, is_wininst): + for args in get_script_args(dist, executable): self.write_script(*args) @@ -550,8 +581,9 @@ class LocalEggInfo(egg_info.egg_info): else: log.info("[pbr] Reusing existing SOURCES.txt") self.filelist = egg_info.FileList() - for entry in open(manifest_filename, 'r').read().split('\n'): - self.filelist.append(entry) + with open(manifest_filename, 'r') as fil: + for entry in fil.read().split('\n'): + self.filelist.append(entry) def _from_git(distribution): @@ -626,6 +658,7 @@ class LocalSDist(sdist.sdist): self.filelist.sort() sdist.sdist.make_distribution(self) + try: from pbr import builddoc _have_sphinx = True @@ -659,12 +692,14 @@ def _get_increment_kwargs(git_dir, tag): # git log output affecting out ability to have working sem ver headers. changelog = git._run_git_command(['log', '--pretty=%B', version_spec], git_dir) - header_len = len('sem-ver:') - commands = [line[header_len:].strip() for line in changelog.split('\n') - if line.lower().startswith('sem-ver:')] symbols = set() - for command in commands: - symbols.update([symbol.strip() for symbol in command.split(',')]) + header = 'sem-ver:' + for line in changelog.split("\n"): + line = line.lower().strip() + if not line.lower().strip().startswith(header): + continue + new_symbols = line[len(header):].strip().split(",") + symbols.update([symbol.strip() for symbol in new_symbols]) def _handle_symbol(symbol, symbols, impact): if symbol in symbols: @@ -791,12 +826,9 @@ def _get_version_from_pkg_metadata(package_name): pkg_metadata = {} for filename in pkg_metadata_filenames: try: - pkg_metadata_file = open(filename, 'r') - except (IOError, OSError): - continue - try: - pkg_metadata = email.message_from_file(pkg_metadata_file) - except email.errors.MessageError: + with open(filename, 'r') as pkg_metadata_file: + pkg_metadata = email.message_from_file(pkg_metadata_file) + except (IOError, OSError, email.errors.MessageError): continue # Check to make sure we're in our own dir diff --git a/libs/common/pbr/tests/base.py b/libs/common/pbr/tests/base.py index 9c409b0a..c94af56b 100644 --- a/libs/common/pbr/tests/base.py +++ b/libs/common/pbr/tests/base.py @@ -187,7 +187,9 @@ class CapturedSubprocess(fixtures.Fixture): self.addDetail(self.label + '-stderr', content.text_content(self.err)) self.returncode = proc.returncode if proc.returncode: - raise AssertionError('Failed process %s' % proc.returncode) + raise AssertionError( + 'Failed process args=%r, kwargs=%r, returncode=%s' % ( + self.args, self.kwargs, proc.returncode)) self.addCleanup(delattr, self, 'out') self.addCleanup(delattr, self, 'err') self.addCleanup(delattr, self, 'returncode') @@ -200,12 +202,15 @@ def _run_cmd(args, cwd): :param cwd: The directory to run the comamnd in. :return: ((stdout, stderr), returncode) """ + print('Running %s' % ' '.join(args)) p = subprocess.Popen( args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) streams = tuple(s.decode('latin1').strip() for s in p.communicate()) - for stream_content in streams: - print(stream_content) + print('STDOUT:') + print(streams[0]) + print('STDERR:') + print(streams[1]) return (streams) + (p.returncode,) diff --git a/libs/common/pbr/tests/test_commands.py b/libs/common/pbr/tests/test_commands.py index 51e27116..7cf3fa91 100644 --- a/libs/common/pbr/tests/test_commands.py +++ b/libs/common/pbr/tests/test_commands.py @@ -78,7 +78,7 @@ class TestCommands(base.BaseTestCase): stdout, stderr, return_code = self.run_pbr('freeze') self.assertEqual(0, return_code) pkgs = [] - for l in stdout.split('\n'): - pkgs.append(l.split('==')[0].lower()) + for line in stdout.split('\n'): + pkgs.append(line.split('==')[0].lower()) pkgs_sort = sorted(pkgs[:]) self.assertEqual(pkgs_sort, pkgs) diff --git a/libs/common/pbr/tests/test_core.py b/libs/common/pbr/tests/test_core.py index 0ee6f532..edb7c7b5 100644 --- a/libs/common/pbr/tests/test_core.py +++ b/libs/common/pbr/tests/test_core.py @@ -40,6 +40,7 @@ import glob import os +import sys import tarfile import fixtures @@ -74,7 +75,7 @@ class TestCore(base.BaseTestCase): self.run_setup('egg_info') stdout, _, _ = self.run_setup('--keywords') - assert stdout == 'packaging,distutils,setuptools' + assert stdout == 'packaging, distutils, setuptools' def test_setup_py_build_sphinx(self): stdout, _, return_code = self.run_setup('build_sphinx') @@ -113,6 +114,12 @@ class TestCore(base.BaseTestCase): def test_console_script_develop(self): """Test that we develop a non-pkg-resources console script.""" + if sys.version_info < (3, 0): + self.skipTest( + 'Fails with recent virtualenv due to ' + 'https://github.com/pypa/virtualenv/issues/1638' + ) + if os.name == 'nt': self.skipTest('Windows support is passthrough') diff --git a/libs/common/pbr/tests/test_files.py b/libs/common/pbr/tests/test_files.py index e60b6ca7..94a2d9ad 100644 --- a/libs/common/pbr/tests/test_files.py +++ b/libs/common/pbr/tests/test_files.py @@ -35,17 +35,31 @@ class FilesConfigTest(base.BaseTestCase): ]) self.useFixture(pkg_fixture) pkg_etc = os.path.join(pkg_fixture.base, 'etc') + pkg_ansible = os.path.join(pkg_fixture.base, 'ansible', + 'kolla-ansible', 'test') + dir_spcs = os.path.join(pkg_fixture.base, 'dir with space') + dir_subdir_spc = os.path.join(pkg_fixture.base, 'multi space', + 'more spaces') pkg_sub = os.path.join(pkg_etc, 'sub') subpackage = os.path.join( pkg_fixture.base, 'fake_package', 'subpackage') os.makedirs(pkg_sub) os.makedirs(subpackage) + os.makedirs(pkg_ansible) + os.makedirs(dir_spcs) + os.makedirs(dir_subdir_spc) with open(os.path.join(pkg_etc, "foo"), 'w') as foo_file: foo_file.write("Foo Data") with open(os.path.join(pkg_sub, "bar"), 'w') as foo_file: foo_file.write("Bar Data") + with open(os.path.join(pkg_ansible, "baz"), 'w') as baz_file: + baz_file.write("Baz Data") with open(os.path.join(subpackage, "__init__.py"), 'w') as foo_file: foo_file.write("# empty") + with open(os.path.join(dir_spcs, "file with spc"), 'w') as spc_file: + spc_file.write("# empty") + with open(os.path.join(dir_subdir_spc, "file with spc"), 'w') as file_: + file_.write("# empty") self.useFixture(base.DiveDir(pkg_fixture.base)) @@ -74,5 +88,61 @@ class FilesConfigTest(base.BaseTestCase): ) files.FilesConfig(config, 'fake_package').run() self.assertIn( - '\netc/pbr/ = \n etc/foo\netc/pbr/sub = \n etc/sub/bar', + "\n'etc/pbr/' = \n 'etc/foo'\n'etc/pbr/sub' = \n 'etc/sub/bar'", + config['files']['data_files']) + + def test_data_files_with_spaces(self): + config = dict( + files=dict( + data_files="\n 'i like spaces' = 'dir with space'/*" + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn( + "\n'i like spaces/' = \n 'dir with space/file with spc'", + config['files']['data_files']) + + def test_data_files_with_spaces_subdirectories(self): + # test that we can handle whitespace in subdirectories + data_files = "\n 'one space/two space' = 'multi space/more spaces'/*" + expected = ( + "\n'one space/two space/' = " + "\n 'multi space/more spaces/file with spc'") + config = dict( + files=dict( + data_files=data_files + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn(expected, config['files']['data_files']) + + def test_data_files_with_spaces_quoted_components(self): + # test that we can quote individual path components + data_files = ( + "\n'one space'/'two space' = 'multi space'/'more spaces'/*" + ) + expected = ("\n'one space/two space/' = " + "\n 'multi space/more spaces/file with spc'") + config = dict( + files=dict( + data_files=data_files + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn(expected, config['files']['data_files']) + + def test_data_files_globbing_source_prefix_in_directory_name(self): + # We want to test that the string, "docs", is not replaced in a + # subdirectory name, "sub-docs" + config = dict( + files=dict( + data_files="\n share/ansible = ansible/*" + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn( + "\n'share/ansible/' = " + "\n'share/ansible/kolla-ansible' = " + "\n'share/ansible/kolla-ansible/test' = " + "\n 'ansible/kolla-ansible/test/baz'", config['files']['data_files']) diff --git a/libs/common/pbr/tests/test_integration.py b/libs/common/pbr/tests/test_integration.py index 8e96f21f..25473b05 100644 --- a/libs/common/pbr/tests/test_integration.py +++ b/libs/common/pbr/tests/test_integration.py @@ -11,7 +11,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +try: + import configparser +except ImportError: + import ConfigParser as configparser import os.path +import pkg_resources import shlex import sys @@ -77,19 +82,35 @@ class TestIntegration(base.BaseTestCase): # We don't break these into separate tests because we'd need separate # source dirs to isolate from side effects of running pip, and the # overheads of setup would start to beat the benefits of parallelism. - self.useFixture(base.CapturedSubprocess( - 'sync-req', - ['python', 'update.py', os.path.join(REPODIR, self.short_name)], - cwd=os.path.join(REPODIR, 'requirements'))) - self.useFixture(base.CapturedSubprocess( - 'commit-requirements', - 'git diff --quiet || git commit -amrequirements', - cwd=os.path.join(REPODIR, self.short_name), shell=True)) - path = os.path.join( - self.useFixture(fixtures.TempDir()).path, 'project') - self.useFixture(base.CapturedSubprocess( - 'clone', - ['git', 'clone', os.path.join(REPODIR, self.short_name), path])) + path = os.path.join(REPODIR, self.short_name) + setup_cfg = os.path.join(path, 'setup.cfg') + project_name = pkg_resources.safe_name(self.short_name).lower() + # These projects should all have setup.cfg files but we'll be careful + if os.path.exists(setup_cfg): + config = configparser.ConfigParser() + config.read(setup_cfg) + if config.has_section('metadata'): + raw_name = config.get('metadata', 'name', + fallback='notapackagename') + # Technically we should really only need to use the raw + # name because all our projects should be good and use + # normalized names but they don't... + project_name = pkg_resources.safe_name(raw_name).lower() + constraints = os.path.join(REPODIR, 'requirements', + 'upper-constraints.txt') + tmp_constraints = os.path.join( + self.useFixture(fixtures.TempDir()).path, + 'upper-constraints.txt') + # We need to filter out the package we are installing to avoid + # conflicts with the constraints. + with open(constraints, 'r') as src: + with open(tmp_constraints, 'w') as dest: + for line in src: + constraint = line.split('===')[0] + if project_name != constraint: + dest.write(line) + pip_cmd = PIP_CMD + ['-c', tmp_constraints] + venv = self.useFixture( test_packaging.Venv('sdist', modules=['pip', 'wheel', PBRVERSION], @@ -105,7 +126,7 @@ class TestIntegration(base.BaseTestCase): filename = os.path.join( path, 'dist', os.listdir(os.path.join(path, 'dist'))[0]) self.useFixture(base.CapturedSubprocess( - 'tarball', [python] + PIP_CMD + [filename])) + 'tarball', [python] + pip_cmd + [filename])) venv = self.useFixture( test_packaging.Venv('install-git', modules=['pip', 'wheel', PBRVERSION], @@ -113,7 +134,7 @@ class TestIntegration(base.BaseTestCase): root = venv.path python = venv.python self.useFixture(base.CapturedSubprocess( - 'install-git', [python] + PIP_CMD + ['git+file://' + path])) + 'install-git', [python] + pip_cmd + ['git+file://' + path])) if self.short_name == 'nova': found = False for _, _, filenames in os.walk(root): @@ -127,7 +148,7 @@ class TestIntegration(base.BaseTestCase): root = venv.path python = venv.python self.useFixture(base.CapturedSubprocess( - 'install-e', [python] + PIP_CMD + ['-e', path])) + 'install-e', [python] + pip_cmd + ['-e', path])) class TestInstallWithoutPbr(base.BaseTestCase): @@ -188,12 +209,16 @@ class TestInstallWithoutPbr(base.BaseTestCase): class TestMarkersPip(base.BaseTestCase): scenarios = [ - ('pip-1.5', {'modules': ['pip>=1.5,<1.6']}), - ('pip-6.0', {'modules': ['pip>=6.0,<6.1']}), ('pip-latest', {'modules': ['pip']}), - ('setuptools-EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8']}), - ('setuptools-Trusty', {'modules': ['pip==1.5', 'setuptools==2.2']}), - ('setuptools-minimum', {'modules': ['pip==1.5', 'setuptools==0.7.2']}), + ('setuptools-Bionic', { + 'modules': ['pip==9.0.1', 'setuptools==39.0.1']}), + ('setuptools-Stretch', { + 'modules': ['pip==9.0.1', 'setuptools==33.1.1']}), + ('setuptools-EL8', {'modules': ['pip==9.0.3', 'setuptools==39.2.0']}), + ('setuptools-Buster', { + 'modules': ['pip==18.1', 'setuptools==40.8.0']}), + ('setuptools-Focal', { + 'modules': ['pip==20.0.2', 'setuptools==45.2.0']}), ] @testtools.skipUnless( @@ -240,25 +265,17 @@ class TestLTSSupport(base.BaseTestCase): # These versions come from the versions installed from the 'virtualenv' # command from the 'python-virtualenv' package. scenarios = [ - ('EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8'], - 'py3support': True}), # And EPEL6 - ('Trusty', {'modules': ['pip==1.5', 'setuptools==2.2'], - 'py3support': True}), - ('Jessie', {'modules': ['pip==1.5.6', 'setuptools==5.5.1'], - 'py3support': True}), - # Wheezy has pip1.1, which cannot be called with '-m pip' - # So we'll use a different version of pip here. - ('WheezyPrecise', {'modules': ['pip==1.4.1', 'setuptools==0.6c11'], - 'py3support': False}) + ('Bionic', {'modules': ['pip==9.0.1', 'setuptools==39.0.1']}), + ('Stretch', {'modules': ['pip==9.0.1', 'setuptools==33.1.1']}), + ('EL8', {'modules': ['pip==9.0.3', 'setuptools==39.2.0']}), + ('Buster', {'modules': ['pip==18.1', 'setuptools==40.8.0']}), + ('Focal', {'modules': ['pip==20.0.2', 'setuptools==45.2.0']}), ] @testtools.skipUnless( os.environ.get('PBR_INTEGRATION', None) == '1', 'integration tests not enabled') def test_lts_venv_default_versions(self): - if (sys.version_info[0] == 3 and not self.py3support): - self.skipTest('This combination will not install with py3, ' - 'skipping test') venv = self.useFixture( test_packaging.Venv('setuptools', modules=self.modules)) bin_python = venv.python diff --git a/libs/common/pbr/tests/test_packaging.py b/libs/common/pbr/tests/test_packaging.py index d19dd05b..c719d1e2 100644 --- a/libs/common/pbr/tests/test_packaging.py +++ b/libs/common/pbr/tests/test_packaging.py @@ -48,7 +48,10 @@ import tempfile import textwrap import fixtures -import mock +try: + from unittest import mock +except ImportError: + import mock import pkg_resources import six import testscenarios @@ -108,7 +111,7 @@ class GPGKeyFixture(fixtures.Fixture): def setUp(self): super(GPGKeyFixture, self).setUp() tempdir = self.useFixture(fixtures.TempDir()) - gnupg_version_re = re.compile('^gpg\s.*\s([\d+])\.([\d+])\.([\d+])') + gnupg_version_re = re.compile(r'^gpg\s.*\s([\d+])\.([\d+])\.([\d+])') gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path) for line in gnupg_version[0].split('\n'): gnupg_version = gnupg_version_re.match(line) @@ -120,9 +123,9 @@ class GPGKeyFixture(fixtures.Fixture): else: if gnupg_version is None: gnupg_version = (0, 0, 0) - config_file = tempdir.path + '/key-config' - f = open(config_file, 'wt') - try: + + config_file = os.path.join(tempdir.path, 'key-config') + with open(config_file, 'wt') as f: if gnupg_version[0] == 2 and gnupg_version[1] >= 1: f.write(""" %no-protection @@ -135,11 +138,9 @@ class GPGKeyFixture(fixtures.Fixture): Name-Comment: N/A Name-Email: example@example.com Expire-Date: 2d - Preferences: (setpref) %commit """) - finally: - f.close() + # Note that --quick-random (--debug-quick-random in GnuPG 2.x) # does not have a corresponding preferences file setting and # must be passed explicitly on the command line instead @@ -149,6 +150,7 @@ class GPGKeyFixture(fixtures.Fixture): gnupg_random = '--debug-quick-random' else: gnupg_random = '' + base._run_cmd( ['gpg', '--gen-key', '--batch', gnupg_random, config_file], tempdir.path) @@ -173,17 +175,17 @@ class Venv(fixtures.Fixture): """ self._reason = reason if modules == (): - pbr = 'file://%s#egg=pbr' % PBR_ROOT - modules = ['pip', 'wheel', pbr] + modules = ['pip', 'wheel', 'build', PBR_ROOT] self.modules = modules if pip_cmd is None: - self.pip_cmd = ['-m', 'pip', 'install'] + self.pip_cmd = ['-m', 'pip', '-v', 'install'] else: self.pip_cmd = pip_cmd def _setUp(self): path = self.useFixture(fixtures.TempDir()).path - virtualenv.create_environment(path, clear=True) + virtualenv.cli_run([path]) + python = os.path.join(path, 'bin', 'python') command = [python] + self.pip_cmd + ['-U'] if self.modules and len(self.modules) > 0: @@ -293,23 +295,23 @@ class TestPackagingInGitRepoWithCommit(base.BaseTestCase): self.run_setup('sdist', allow_fail=False) with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: body = f.read() - self.assertIn('\*', body) + self.assertIn(r'\*', body) def test_changelog_handles_dead_links_in_commit(self): self.repo.commit(message_content="See os_ for to_do about qemu_.") self.run_setup('sdist', allow_fail=False) with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: body = f.read() - self.assertIn('os\_', body) - self.assertIn('to\_do', body) - self.assertIn('qemu\_', body) + self.assertIn(r'os\_', body) + self.assertIn(r'to\_do', body) + self.assertIn(r'qemu\_', body) def test_changelog_handles_backticks(self): self.repo.commit(message_content="Allow `openstack.org` to `work") self.run_setup('sdist', allow_fail=False) with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: body = f.read() - self.assertIn('\`', body) + self.assertIn(r'\`', body) def test_manifest_exclude_honoured(self): self.run_setup('sdist', allow_fail=False) @@ -379,6 +381,12 @@ class TestPackagingWheels(base.BaseTestCase): wheel_file.extractall(self.extracted_wheel_dir) wheel_file.close() + def test_metadata_directory_has_pbr_json(self): + # Build the path to the scripts directory + pbr_json = os.path.join( + self.extracted_wheel_dir, 'pbr_testpackage-0.0.dist-info/pbr.json') + self.assertTrue(os.path.exists(pbr_json)) + def test_data_directory_has_wsgi_scripts(self): # Build the path to the scripts directory scripts_dir = os.path.join( @@ -531,11 +539,13 @@ class ParseRequirementsTest(base.BaseTestCase): tempdir = tempfile.mkdtemp() requirements = os.path.join(tempdir, 'requirements.txt') with open(requirements, 'w') as f: - f.write('-i https://myindex.local') - f.write(' --index-url https://myindex.local') - f.write(' --extra-index-url https://myindex.local') + f.write('-i https://myindex.local\n') + f.write(' --index-url https://myindex.local\n') + f.write(' --extra-index-url https://myindex.local\n') + f.write('--find-links https://myindex.local\n') + f.write('arequirement>=1.0\n') result = packaging.parse_requirements([requirements]) - self.assertEqual([], result) + self.assertEqual(['arequirement>=1.0'], result) def test_nested_requirements(self): tempdir = tempfile.mkdtemp() @@ -662,12 +672,65 @@ class TestVersions(base.BaseTestCase): version = packaging._get_version_from_git() self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + def test_multi_inline_symbols_no_space(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('Sem-ver: feature,api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_multi_inline_symbols_spaced(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('Sem-ver: feature, api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_multi_inline_symbols_reversed(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('Sem-ver: api-break,feature') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_leading_space(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit(' sem-ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_leading_space_multiline(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit( + ( + ' Some cool text\n' + ' sem-ver: api-break' + ) + ) + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_leading_characters_symbol_not_found(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit(' ssem-ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.4.dev1')) + def test_tagged_version_has_tag_version(self): self.repo.commit() self.repo.tag('1.2.3') version = packaging._get_version_from_git('1.2.3') self.assertEqual('1.2.3', version) + def test_tagged_version_with_semver_compliant_prerelease(self): + self.repo.commit() + self.repo.tag('1.2.3-rc2') + version = packaging._get_version_from_git() + self.assertEqual('1.2.3.0rc2', version) + def test_non_canonical_tagged_version_bump(self): self.repo.commit() self.repo.tag('1.4') @@ -724,6 +787,13 @@ class TestVersions(base.BaseTestCase): version = packaging._get_version_from_git('1.2.3') self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1')) + def test_untagged_version_after_semver_compliant_prerelease_tag(self): + self.repo.commit() + self.repo.tag('1.2.3-rc2') + self.repo.commit() + version = packaging._get_version_from_git() + self.assertEqual('1.2.3.0rc3.dev1', version) + def test_preversion_too_low_simple(self): # That is, the target version is either already released or not high # enough for the semver requirements given api breaks etc. @@ -750,8 +820,10 @@ class TestVersions(base.BaseTestCase): def test_get_kwargs_corner_cases(self): # No tags: - git_dir = self.repo._basedir + '/.git' - get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag) + + def get_kwargs(tag): + git_dir = self.repo._basedir + '/.git' + return packaging._get_increment_kwargs(git_dir, tag) def _check_combinations(tag): self.repo.commit() @@ -903,6 +975,235 @@ class TestRequirementParsing(base.BaseTestCase): self.assertEqual(exp_parsed, gen_parsed) +class TestPEP517Support(base.BaseTestCase): + def test_pep_517_support(self): + # Note that the current PBR PEP517 entrypoints rely on a valid + # PBR setup.py existing. + pkgs = { + 'test_pep517': + { + 'requirements.txt': textwrap.dedent("""\ + sphinx + iso8601 + """), + # Override default setup.py to remove setup_requires. + 'setup.py': textwrap.dedent("""\ + #!/usr/bin/env python + import setuptools + setuptools.setup(pbr=True) + """), + 'setup.cfg': textwrap.dedent("""\ + [metadata] + name = test_pep517 + summary = A tiny test project + author = PBR Team + author-email = foo@example.com + home-page = https://example.com/ + classifier = + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + """), + 'pyproject.toml': textwrap.dedent("""\ + [build-system] + requires = ["pbr", "setuptools>=36.6.0", "wheel"] + build-backend = "pbr.build" + """)}, + } + pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs + pkg_dir = pkg_dirs['test_pep517'] + venv = self.useFixture(Venv('PEP517')) + + # Test building sdists and wheels works. Note we do not use pip here + # because pip will forcefully install the latest version of PBR on + # pypi to satisfy the build-system requires. This means we can't self + # test changes using pip. Build with --no-isolation appears to avoid + # this problem. + self._run_cmd(venv.python, ('-m', 'build', '--no-isolation', '.'), + allow_fail=False, cwd=pkg_dir) + + +class TestRepositoryURLDependencies(base.BaseTestCase): + + def setUp(self): + super(TestRepositoryURLDependencies, self).setUp() + self.requirements = os.path.join(tempfile.mkdtemp(), + 'requirements.txt') + with open(self.requirements, 'w') as f: + f.write('\n'.join([ + '-e git+git://git.pro-ject.org/oslo.messaging#egg=oslo.messaging-1.0.0-rc', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize-beta', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-4.0.1', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha.beta.1', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa + '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-2.0.0-rc.1+build.123', # noqa + '-e git+git://git.project.org/Proj#egg=Proj1', + 'git+https://git.project.org/Proj#egg=Proj2-0.0.1', + '-e git+ssh://git.project.org/Proj#egg=Proj3', + 'svn+svn://svn.project.org/svn/Proj#egg=Proj4-0.0.2', + '-e svn+http://svn.project.org/svn/Proj/trunk@2019#egg=Proj5', + 'hg+http://hg.project.org/Proj@da39a3ee5e6b#egg=Proj-0.0.3', + '-e hg+http://hg.project.org/Proj@2019#egg=Proj', + 'hg+http://hg.project.org/Proj@v1.0#egg=Proj-0.0.4', + '-e hg+http://hg.project.org/Proj@special_feature#egg=Proj', + 'git://foo.com/zipball#egg=foo-bar-1.2.4', + 'pypi-proj1', 'pypi-proj2'])) + + def test_egg_fragment(self): + expected = [ + 'django-thumborize', + 'django-thumborize-beta', + 'django-thumborize2-beta', + 'django-thumborize2-beta>=4.0.1', + 'django-thumborize2-beta>=1.0.0-alpha.beta.1', + 'django-thumborize2-beta>=1.0.0-alpha-a.b-c-long+build.1-aef.1-its-okay', # noqa + 'django-thumborize2-beta>=2.0.0-rc.1+build.123', + 'django-thumborize-beta>=0.0.4', + 'django-thumborize-beta>=1.2.3', + 'django-thumborize-beta>=10.20.30', + 'django-thumborize-beta>=1.1.2-prerelease+meta', + 'django-thumborize-beta>=1.1.2+meta', + 'django-thumborize-beta>=1.1.2+meta-valid', + 'django-thumborize-beta>=1.0.0-alpha', + 'django-thumborize-beta>=1.0.0-beta', + 'django-thumborize-beta>=1.0.0-alpha.beta', + 'django-thumborize-beta>=1.0.0-alpha.beta.1', + 'django-thumborize-beta>=1.0.0-alpha.1', + 'django-thumborize-beta>=1.0.0-alpha0.valid', + 'django-thumborize-beta>=1.0.0-alpha.0valid', + 'django-thumborize-beta>=1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa + 'django-thumborize-beta>=1.0.0-rc.1+build.1', + 'django-thumborize-beta>=2.0.0-rc.1+build.123', + 'django-thumborize-beta>=1.2.3-beta', + 'django-thumborize-beta>=10.2.3-DEV-SNAPSHOT', + 'django-thumborize-beta>=1.2.3-SNAPSHOT-123', + 'django-thumborize-beta>=1.0.0', + 'django-thumborize-beta>=2.0.0', + 'django-thumborize-beta>=1.1.7', + 'django-thumborize-beta>=2.0.0+build.1848', + 'django-thumborize-beta>=2.0.1-alpha.1227', + 'django-thumborize-beta>=1.0.0-alpha+beta', + 'django-thumborize-beta>=1.2.3----RC-SNAPSHOT.12.9.1--.12+788', + 'django-thumborize-beta>=1.2.3----R-S.12.9.1--.12+meta', + 'django-thumborize-beta>=1.2.3----RC-SNAPSHOT.12.9.1--.12', + 'django-thumborize-beta>=1.0.0+0.build.1-rc.10000aaa-kk-0.1', + 'django-thumborize-beta>=999999999999999999.99999999999999.9999999999999', # noqa + 'Proj1', + 'Proj2>=0.0.1', + 'Proj3', + 'Proj4>=0.0.2', + 'Proj5', + 'Proj>=0.0.3', + 'Proj', + 'Proj>=0.0.4', + 'Proj', + 'foo-bar>=1.2.4', + ] + tests = [ + 'egg=django-thumborize', + 'egg=django-thumborize-beta', + 'egg=django-thumborize2-beta', + 'egg=django-thumborize2-beta-4.0.1', + 'egg=django-thumborize2-beta-1.0.0-alpha.beta.1', + 'egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-long+build.1-aef.1-its-okay', # noqa + 'egg=django-thumborize2-beta-2.0.0-rc.1+build.123', + 'egg=django-thumborize-beta-0.0.4', + 'egg=django-thumborize-beta-1.2.3', + 'egg=django-thumborize-beta-10.20.30', + 'egg=django-thumborize-beta-1.1.2-prerelease+meta', + 'egg=django-thumborize-beta-1.1.2+meta', + 'egg=django-thumborize-beta-1.1.2+meta-valid', + 'egg=django-thumborize-beta-1.0.0-alpha', + 'egg=django-thumborize-beta-1.0.0-beta', + 'egg=django-thumborize-beta-1.0.0-alpha.beta', + 'egg=django-thumborize-beta-1.0.0-alpha.beta.1', + 'egg=django-thumborize-beta-1.0.0-alpha.1', + 'egg=django-thumborize-beta-1.0.0-alpha0.valid', + 'egg=django-thumborize-beta-1.0.0-alpha.0valid', + 'egg=django-thumborize-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa + 'egg=django-thumborize-beta-1.0.0-rc.1+build.1', + 'egg=django-thumborize-beta-2.0.0-rc.1+build.123', + 'egg=django-thumborize-beta-1.2.3-beta', + 'egg=django-thumborize-beta-10.2.3-DEV-SNAPSHOT', + 'egg=django-thumborize-beta-1.2.3-SNAPSHOT-123', + 'egg=django-thumborize-beta-1.0.0', + 'egg=django-thumborize-beta-2.0.0', + 'egg=django-thumborize-beta-1.1.7', + 'egg=django-thumborize-beta-2.0.0+build.1848', + 'egg=django-thumborize-beta-2.0.1-alpha.1227', + 'egg=django-thumborize-beta-1.0.0-alpha+beta', + 'egg=django-thumborize-beta-1.2.3----RC-SNAPSHOT.12.9.1--.12+788', # noqa + 'egg=django-thumborize-beta-1.2.3----R-S.12.9.1--.12+meta', + 'egg=django-thumborize-beta-1.2.3----RC-SNAPSHOT.12.9.1--.12', + 'egg=django-thumborize-beta-1.0.0+0.build.1-rc.10000aaa-kk-0.1', # noqa + 'egg=django-thumborize-beta-999999999999999999.99999999999999.9999999999999', # noqa + 'egg=Proj1', + 'egg=Proj2-0.0.1', + 'egg=Proj3', + 'egg=Proj4-0.0.2', + 'egg=Proj5', + 'egg=Proj-0.0.3', + 'egg=Proj', + 'egg=Proj-0.0.4', + 'egg=Proj', + 'egg=foo-bar-1.2.4', + ] + for index, test in enumerate(tests): + self.assertEqual(expected[index], + re.sub(r'egg=([^&]+).*$', + packaging.egg_fragment, + test)) + + def test_parse_repo_url_requirements(self): + result = packaging.parse_requirements([self.requirements]) + self.assertEqual(['oslo.messaging>=1.0.0-rc', + 'django-thumborize', + 'django-thumborize-beta', + 'django-thumborize2-beta', + 'django-thumborize2-beta>=4.0.1', + 'django-thumborize2-beta>=1.0.0-alpha.beta.1', + 'django-thumborize2-beta>=1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa + 'django-thumborize2-beta>=2.0.0-rc.1+build.123', + 'Proj1', 'Proj2>=0.0.1', 'Proj3', + 'Proj4>=0.0.2', 'Proj5', 'Proj>=0.0.3', + 'Proj', 'Proj>=0.0.4', 'Proj', + 'foo-bar>=1.2.4', 'pypi-proj1', + 'pypi-proj2'], result) + + def test_parse_repo_url_dependency_links(self): + result = packaging.parse_dependency_links([self.requirements]) + self.assertEqual( + [ + 'git+git://git.pro-ject.org/oslo.messaging#egg=oslo.messaging-1.0.0-rc', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize-beta', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-4.0.1', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha.beta.1', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa + 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-2.0.0-rc.1+build.123', # noqa + 'git+git://git.project.org/Proj#egg=Proj1', + 'git+https://git.project.org/Proj#egg=Proj2-0.0.1', + 'git+ssh://git.project.org/Proj#egg=Proj3', + 'svn+svn://svn.project.org/svn/Proj#egg=Proj4-0.0.2', + 'svn+http://svn.project.org/svn/Proj/trunk@2019#egg=Proj5', + 'hg+http://hg.project.org/Proj@da39a3ee5e6b#egg=Proj-0.0.3', + 'hg+http://hg.project.org/Proj@2019#egg=Proj', + 'hg+http://hg.project.org/Proj@v1.0#egg=Proj-0.0.4', + 'hg+http://hg.project.org/Proj@special_feature#egg=Proj', + 'git://foo.com/zipball#egg=foo-bar-1.2.4'], result) + + def get_soabi(): soabi = None try: diff --git a/libs/common/pbr/tests/test_pbr_json.py b/libs/common/pbr/tests/test_pbr_json.py index f0669713..eb9a08ad 100644 --- a/libs/common/pbr/tests/test_pbr_json.py +++ b/libs/common/pbr/tests/test_pbr_json.py @@ -10,7 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +try: + from unittest import mock +except ImportError: + import mock from pbr import pbr_json from pbr.tests import base diff --git a/libs/common/pbr/tests/test_setup.py b/libs/common/pbr/tests/test_setup.py index 85d40ebf..43077088 100644 --- a/libs/common/pbr/tests/test_setup.py +++ b/libs/common/pbr/tests/test_setup.py @@ -93,8 +93,9 @@ class SkipFileWrites(base.BaseTestCase): option_dict=self.option_dict) self.assertEqual( not os.path.exists(self.filename), - (self.option_value.lower() in options.TRUE_VALUES - or self.env_value is not None)) + (self.option_value.lower() in options.TRUE_VALUES or + self.env_value is not None)) + _changelog_content = """7780758\x00Break parser\x00 (tag: refs/tags/1_foo.1) 04316fe\x00Make python\x00 (refs/heads/review/monty_taylor/27519) @@ -125,6 +126,7 @@ def _make_old_git_changelog_format(line): refname = refname.replace('tag: ', '') return '\x00'.join((sha, msg, refname)) + _old_git_changelog_content = '\n'.join( _make_old_git_changelog_format(line) for line in _changelog_content.split('\n')) @@ -162,7 +164,7 @@ class GitLogsTest(base.BaseTestCase): self.assertIn("------", changelog_contents) self.assertIn("Refactor hooks file", changelog_contents) self.assertIn( - "Bug fix: create\_stack() fails when waiting", + r"Bug fix: create\_stack() fails when waiting", changelog_contents) self.assertNotIn("Refactor hooks file.", changelog_contents) self.assertNotIn("182feb3", changelog_contents) @@ -176,7 +178,7 @@ class GitLogsTest(base.BaseTestCase): self.assertNotIn("ev)il", changelog_contents) self.assertNotIn("e(vi)l", changelog_contents) self.assertNotIn('Merge "', changelog_contents) - self.assertNotIn('1\_foo.1', changelog_contents) + self.assertNotIn(r'1\_foo.1', changelog_contents) def test_generate_authors(self): author_old = u"Foo Foo " @@ -216,9 +218,9 @@ class GitLogsTest(base.BaseTestCase): with open(os.path.join(self.temp_path, "AUTHORS"), "r") as auth_fh: authors = auth_fh.read() - self.assertTrue(author_old in authors) - self.assertTrue(author_new in authors) - self.assertTrue(co_author in authors) + self.assertIn(author_old, authors) + self.assertIn(author_new, authors) + self.assertIn(co_author, authors) class _SphinxConfig(object): diff --git a/libs/common/pbr/tests/test_util.py b/libs/common/pbr/tests/test_util.py index 370a7dee..b25d0c77 100644 --- a/libs/common/pbr/tests/test_util.py +++ b/libs/common/pbr/tests/test_util.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP) # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,6 +14,7 @@ # under the License. import io +import tempfile import textwrap import six @@ -23,6 +25,122 @@ from pbr.tests import base from pbr import util +def config_from_ini(ini): + config = {} + ini = textwrap.dedent(six.u(ini)) + if sys.version_info >= (3, 2): + parser = configparser.ConfigParser() + parser.read_file(io.StringIO(ini)) + else: + parser = configparser.SafeConfigParser() + parser.readfp(io.StringIO(ini)) + for section in parser.sections(): + config[section] = dict(parser.items(section)) + return config + + +class TestBasics(base.BaseTestCase): + + def test_basics(self): + self.maxDiff = None + config_text = """ + [metadata] + name = foo + version = 1.0 + author = John Doe + author_email = jd@example.com + maintainer = Jim Burke + maintainer_email = jb@example.com + home_page = http://example.com + summary = A foobar project. + description = Hello, world. This is a long description. + download_url = http://opendev.org/x/pbr + classifier = + Development Status :: 5 - Production/Stable + Programming Language :: Python + platform = + any + license = Apache 2.0 + requires_dist = + Sphinx + requests + setup_requires_dist = + docutils + python_requires = >=3.6 + provides_dist = + bax + provides_extras = + bar + obsoletes_dist = + baz + + [files] + packages_root = src + packages = + foo + package_data = + "" = *.txt, *.rst + foo = *.msg + namespace_packages = + hello + data_files = + bitmaps = + bm/b1.gif + bm/b2.gif + config = + cfg/data.cfg + scripts = + scripts/hello-world.py + modules = + mod1 + """ + expected = { + 'name': u'foo', + 'version': u'1.0', + 'author': u'John Doe', + 'author_email': u'jd@example.com', + 'maintainer': u'Jim Burke', + 'maintainer_email': u'jb@example.com', + 'url': u'http://example.com', + 'description': u'A foobar project.', + 'long_description': u'Hello, world. This is a long description.', + 'download_url': u'http://opendev.org/x/pbr', + 'classifiers': [ + u'Development Status :: 5 - Production/Stable', + u'Programming Language :: Python', + ], + 'platforms': [u'any'], + 'license': u'Apache 2.0', + 'install_requires': [ + u'Sphinx', + u'requests', + ], + 'setup_requires': [u'docutils'], + 'python_requires': u'>=3.6', + 'provides': [u'bax'], + 'provides_extras': [u'bar'], + 'obsoletes': [u'baz'], + 'extras_require': {}, + + 'package_dir': {'': u'src'}, + 'packages': [u'foo'], + 'package_data': { + '': ['*.txt,', '*.rst'], + 'foo': ['*.msg'], + }, + 'namespace_packages': [u'hello'], + 'data_files': [ + ('bitmaps', ['bm/b1.gif', 'bm/b2.gif']), + ('config', ['cfg/data.cfg']), + ], + 'scripts': [u'scripts/hello-world.py'], + 'py_modules': [u'mod1'], + } + config = config_from_ini(config_text) + actual = util.setup_cfg_to_setup_kwargs(config) + self.assertDictEqual(expected, actual) + + class TestExtrasRequireParsingScenarios(base.BaseTestCase): scenarios = [ @@ -64,20 +182,8 @@ class TestExtrasRequireParsingScenarios(base.BaseTestCase): {} })] - def config_from_ini(self, ini): - config = {} - if sys.version_info >= (3, 2): - parser = configparser.ConfigParser() - else: - parser = configparser.SafeConfigParser() - ini = textwrap.dedent(six.u(ini)) - parser.readfp(io.StringIO(ini)) - for section in parser.sections(): - config[section] = dict(parser.items(section)) - return config - def test_extras_parsing(self): - config = self.config_from_ini(self.config_text) + config = config_from_ini(self.config_text) kwargs = util.setup_cfg_to_setup_kwargs(config) self.assertEqual(self.expected_extra_requires, @@ -89,3 +195,127 @@ class TestInvalidMarkers(base.BaseTestCase): def test_invalid_marker_raises_error(self): config = {'extras': {'test': "foo :bad_marker>'1.0'"}} self.assertRaises(SyntaxError, util.setup_cfg_to_setup_kwargs, config) + + +class TestMapFieldsParsingScenarios(base.BaseTestCase): + + scenarios = [ + ('simple_project_urls', { + 'config_text': """ + [metadata] + project_urls = + Bug Tracker = https://bugs.launchpad.net/pbr/ + Documentation = https://docs.openstack.org/pbr/ + Source Code = https://opendev.org/openstack/pbr + """, # noqa: E501 + 'expected_project_urls': { + 'Bug Tracker': 'https://bugs.launchpad.net/pbr/', + 'Documentation': 'https://docs.openstack.org/pbr/', + 'Source Code': 'https://opendev.org/openstack/pbr', + }, + }), + ('query_parameters', { + 'config_text': """ + [metadata] + project_urls = + Bug Tracker = https://bugs.launchpad.net/pbr/?query=true + Documentation = https://docs.openstack.org/pbr/?foo=bar + Source Code = https://git.openstack.org/cgit/openstack-dev/pbr/commit/?id=hash + """, # noqa: E501 + 'expected_project_urls': { + 'Bug Tracker': 'https://bugs.launchpad.net/pbr/?query=true', + 'Documentation': 'https://docs.openstack.org/pbr/?foo=bar', + 'Source Code': 'https://git.openstack.org/cgit/openstack-dev/pbr/commit/?id=hash', # noqa: E501 + }, + }), + ] + + def test_project_url_parsing(self): + config = config_from_ini(self.config_text) + kwargs = util.setup_cfg_to_setup_kwargs(config) + + self.assertEqual(self.expected_project_urls, kwargs['project_urls']) + + +class TestKeywordsParsingScenarios(base.BaseTestCase): + + scenarios = [ + ('keywords_list', { + 'config_text': """ + [metadata] + keywords = + one + two + three + """, # noqa: E501 + 'expected_keywords': ['one', 'two', 'three'], + }, + ), + ('inline_keywords', { + 'config_text': """ + [metadata] + keywords = one, two, three + """, # noqa: E501 + 'expected_keywords': ['one, two, three'], + }), + ] + + def test_keywords_parsing(self): + config = config_from_ini(self.config_text) + kwargs = util.setup_cfg_to_setup_kwargs(config) + + self.assertEqual(self.expected_keywords, kwargs['keywords']) + + +class TestProvidesExtras(base.BaseTestCase): + def test_provides_extras(self): + ini = """ + [metadata] + provides_extras = foo + bar + """ + config = config_from_ini(ini) + kwargs = util.setup_cfg_to_setup_kwargs(config) + self.assertEqual(['foo', 'bar'], kwargs['provides_extras']) + + +class TestDataFilesParsing(base.BaseTestCase): + + scenarios = [ + ('data_files', { + 'config_text': """ + [files] + data_files = + 'i like spaces/' = + 'dir with space/file with spc 2' + 'dir with space/file with spc 1' + """, + 'data_files': [ + ('i like spaces/', ['dir with space/file with spc 2', + 'dir with space/file with spc 1']) + ] + })] + + def test_handling_of_whitespace_in_data_files(self): + config = config_from_ini(self.config_text) + kwargs = util.setup_cfg_to_setup_kwargs(config) + + self.assertEqual(self.data_files, kwargs['data_files']) + + +class TestUTF8DescriptionFile(base.BaseTestCase): + def test_utf8_description_file(self): + _, path = tempfile.mkstemp() + ini_template = """ + [metadata] + description_file = %s + """ + # Two \n's because pbr strips the file content and adds \n\n + # This way we can use it directly as the assert comparison + unicode_description = u'UTF8 description: é"…-ʃŋ\'\n\n' + ini = ini_template % path + with io.open(path, 'w', encoding='utf8') as f: + f.write(unicode_description) + config = config_from_ini(ini) + kwargs = util.setup_cfg_to_setup_kwargs(config) + self.assertEqual(unicode_description, kwargs['long_description']) diff --git a/libs/common/pbr/tests/test_wsgi.py b/libs/common/pbr/tests/test_wsgi.py index f840610d..a42fe785 100644 --- a/libs/common/pbr/tests/test_wsgi.py +++ b/libs/common/pbr/tests/test_wsgi.py @@ -77,8 +77,8 @@ class TestWsgiScripts(base.BaseTestCase): def _test_wsgi(self, cmd_name, output, extra_args=None): cmd = os.path.join(self.temp_dir, 'bin', cmd_name) - print("Running %s -p 0" % cmd) - popen_cmd = [cmd, '-p', '0'] + print("Running %s -p 0 -b 127.0.0.1" % cmd) + popen_cmd = [cmd, '-p', '0', '-b', '127.0.0.1'] if extra_args: popen_cmd.extend(extra_args) @@ -98,7 +98,7 @@ class TestWsgiScripts(base.BaseTestCase): stdoutdata = p.stdout.readline() # Available at ... print(stdoutdata) - m = re.search(b'(http://[^:]+:\d+)/', stdoutdata) + m = re.search(br'(http://[^:]+:\d+)/', stdoutdata) self.assertIsNotNone(m, "Regex failed to match on %s" % stdoutdata) stdoutdata = p.stdout.readline() # DANGER! ... diff --git a/libs/common/pbr/tests/testpackage/doc/source/conf.py b/libs/common/pbr/tests/testpackage/doc/source/conf.py index 73585100..6edbe8e3 100644 --- a/libs/common/pbr/tests/testpackage/doc/source/conf.py +++ b/libs/common/pbr/tests/testpackage/doc/source/conf.py @@ -12,17 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import sys -sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', - #'sphinx.ext.intersphinx', ] # autodoc generation is a bit aggressive and a nuisance when doing heavy @@ -49,17 +45,9 @@ add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' + # -- Options for HTML output -------------------------------------------------- -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). @@ -69,6 +57,3 @@ latex_documents = [ u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] - -# Example configuration for intersphinx: refer to the Python standard library. -#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/libs/common/pbr/tests/util.py b/libs/common/pbr/tests/util.py index 0e7bcf15..8a00c840 100644 --- a/libs/common/pbr/tests/util.py +++ b/libs/common/pbr/tests/util.py @@ -53,9 +53,9 @@ except ImportError: @contextlib.contextmanager def open_config(filename): if sys.version_info >= (3, 2): - cfg = configparser.ConfigParser() + cfg = configparser.ConfigParser() else: - cfg = configparser.SafeConfigParser() + cfg = configparser.SafeConfigParser() cfg.read(filename) yield cfg with open(filename, 'w') as fp: diff --git a/libs/common/pbr/util.py b/libs/common/pbr/util.py index 63e913d9..0669a246 100644 --- a/libs/common/pbr/util.py +++ b/libs/common/pbr/util.py @@ -62,8 +62,10 @@ except ImportError: import logging # noqa from collections import defaultdict +import io import os import re +import shlex import sys import traceback @@ -86,50 +88,52 @@ import pbr.hooks # predicates in () _VERSION_SPEC_RE = re.compile(r'\s*(.*?)\s*\((.*)\)\s*$') - -# Mappings from setup() keyword arguments to setup.cfg options; -# The values are (section, option) tuples, or simply (section,) tuples if -# the option has the same name as the setup() argument -D1_D2_SETUP_ARGS = { - "name": ("metadata",), - "version": ("metadata",), - "author": ("metadata",), - "author_email": ("metadata",), - "maintainer": ("metadata",), - "maintainer_email": ("metadata",), - "url": ("metadata", "home_page"), - "project_urls": ("metadata",), - "description": ("metadata", "summary"), - "keywords": ("metadata",), - "long_description": ("metadata", "description"), - "long_description_content_type": ("metadata", "description_content_type"), - "download_url": ("metadata",), - "classifiers": ("metadata", "classifier"), - "platforms": ("metadata", "platform"), # ** - "license": ("metadata",), +# Mappings from setup.cfg options, in (section, option) form, to setup() +# keyword arguments +CFG_TO_PY_SETUP_ARGS = ( + (('metadata', 'name'), 'name'), + (('metadata', 'version'), 'version'), + (('metadata', 'author'), 'author'), + (('metadata', 'author_email'), 'author_email'), + (('metadata', 'maintainer'), 'maintainer'), + (('metadata', 'maintainer_email'), 'maintainer_email'), + (('metadata', 'home_page'), 'url'), + (('metadata', 'project_urls'), 'project_urls'), + (('metadata', 'summary'), 'description'), + (('metadata', 'keywords'), 'keywords'), + (('metadata', 'description'), 'long_description'), + ( + ('metadata', 'description_content_type'), + 'long_description_content_type', + ), + (('metadata', 'download_url'), 'download_url'), + (('metadata', 'classifier'), 'classifiers'), + (('metadata', 'platform'), 'platforms'), # ** + (('metadata', 'license'), 'license'), # Use setuptools install_requires, not # broken distutils requires - "install_requires": ("metadata", "requires_dist"), - "setup_requires": ("metadata", "setup_requires_dist"), - "python_requires": ("metadata",), - "provides": ("metadata", "provides_dist"), # ** - "obsoletes": ("metadata", "obsoletes_dist"), # ** - "package_dir": ("files", 'packages_root'), - "packages": ("files",), - "package_data": ("files",), - "namespace_packages": ("files",), - "data_files": ("files",), - "scripts": ("files",), - "py_modules": ("files", "modules"), # ** - "cmdclass": ("global", "commands"), + (('metadata', 'requires_dist'), 'install_requires'), + (('metadata', 'setup_requires_dist'), 'setup_requires'), + (('metadata', 'python_requires'), 'python_requires'), + (('metadata', 'requires_python'), 'python_requires'), + (('metadata', 'provides_dist'), 'provides'), # ** + (('metadata', 'provides_extras'), 'provides_extras'), + (('metadata', 'obsoletes_dist'), 'obsoletes'), # ** + (('files', 'packages_root'), 'package_dir'), + (('files', 'packages'), 'packages'), + (('files', 'package_data'), 'package_data'), + (('files', 'namespace_packages'), 'namespace_packages'), + (('files', 'data_files'), 'data_files'), + (('files', 'scripts'), 'scripts'), + (('files', 'modules'), 'py_modules'), # ** + (('global', 'commands'), 'cmdclass'), # Not supported in distutils2, but provided for # backwards compatibility with setuptools - "use_2to3": ("backwards_compat", "use_2to3"), - "zip_safe": ("backwards_compat", "zip_safe"), - "tests_require": ("backwards_compat", "tests_require"), - "dependency_links": ("backwards_compat",), - "include_package_data": ("backwards_compat",), -} + (('backwards_compat', 'zip_safe'), 'zip_safe'), + (('backwards_compat', 'tests_require'), 'tests_require'), + (('backwards_compat', 'dependency_links'), 'dependency_links'), + (('backwards_compat', 'include_package_data'), 'include_package_data'), +) # setup() arguments that can have multiple values in setup.cfg MULTI_FIELDS = ("classifiers", @@ -146,16 +150,27 @@ MULTI_FIELDS = ("classifiers", "dependency_links", "setup_requires", "tests_require", - "cmdclass") + "keywords", + "cmdclass", + "provides_extras") # setup() arguments that can have mapping values in setup.cfg MAP_FIELDS = ("project_urls",) # setup() arguments that contain boolean values -BOOL_FIELDS = ("use_2to3", "zip_safe", "include_package_data") +BOOL_FIELDS = ("zip_safe", "include_package_data") + +CSV_FIELDS = () -CSV_FIELDS = ("keywords",) +def shlex_split(path): + if os.name == 'nt': + # shlex cannot handle paths that contain backslashes, treating those + # as escape characters. + path = path.replace("\\", "/") + return [x.replace("/", "\\") for x in shlex.split(path)] + + return shlex.split(path) def resolve_name(name): @@ -205,10 +220,11 @@ def cfg_to_args(path='setup.cfg', script_args=()): """ # The method source code really starts here. - if sys.version_info >= (3, 2): - parser = configparser.ConfigParser() + if sys.version_info >= (3, 0): + parser = configparser.ConfigParser() else: - parser = configparser.SafeConfigParser() + parser = configparser.SafeConfigParser() + if not os.path.exists(path): raise errors.DistutilsFileError("file '%s' does not exist" % os.path.abspath(path)) @@ -297,34 +313,25 @@ def setup_cfg_to_setup_kwargs(config, script_args=()): # parse env_markers. all_requirements = {} - for arg in D1_D2_SETUP_ARGS: - if len(D1_D2_SETUP_ARGS[arg]) == 2: - # The distutils field name is different than distutils2's. - section, option = D1_D2_SETUP_ARGS[arg] - - elif len(D1_D2_SETUP_ARGS[arg]) == 1: - # The distutils field name is the same thant distutils2's. - section = D1_D2_SETUP_ARGS[arg][0] - option = arg + for alias, arg in CFG_TO_PY_SETUP_ARGS: + section, option = alias in_cfg_value = has_get_option(config, section, option) + if not in_cfg_value and arg == "long_description": + in_cfg_value = has_get_option(config, section, "description_file") + if in_cfg_value: + in_cfg_value = split_multiline(in_cfg_value) + value = '' + for filename in in_cfg_value: + description_file = io.open(filename, encoding='utf-8') + try: + value += description_file.read().strip() + '\n\n' + finally: + description_file.close() + in_cfg_value = value + if not in_cfg_value: - # There is no such option in the setup.cfg - if arg == "long_description": - in_cfg_value = has_get_option(config, section, - "description_file") - if in_cfg_value: - in_cfg_value = split_multiline(in_cfg_value) - value = '' - for filename in in_cfg_value: - description_file = open(filename) - try: - value += description_file.read().strip() + '\n\n' - finally: - description_file.close() - in_cfg_value = value - else: - continue + continue if arg in CSV_FIELDS: in_cfg_value = split_csv(in_cfg_value) @@ -333,7 +340,7 @@ def setup_cfg_to_setup_kwargs(config, script_args=()): elif arg in MAP_FIELDS: in_cfg_map = {} for i in split_multiline(in_cfg_value): - k, v = i.split('=') + k, v = i.split('=', 1) in_cfg_map[k.strip()] = v.strip() in_cfg_value = in_cfg_map elif arg in BOOL_FIELDS: @@ -370,26 +377,27 @@ def setup_cfg_to_setup_kwargs(config, script_args=()): for line in in_cfg_value: if '=' in line: key, value = line.split('=', 1) - key, value = (key.strip(), value.strip()) + key_unquoted = shlex_split(key.strip())[0] + key, value = (key_unquoted, value.strip()) if key in data_files: # Multiple duplicates of the same package name; # this is for backwards compatibility of the old # format prior to d2to1 0.2.6. prev = data_files[key] - prev.extend(value.split()) + prev.extend(shlex_split(value)) else: - prev = data_files[key.strip()] = value.split() + prev = data_files[key.strip()] = shlex_split(value) elif firstline: raise errors.DistutilsOptionError( 'malformed package_data first line %r (misses ' '"=")' % line) else: - prev.extend(line.strip().split()) + prev.extend(shlex_split(line.strip())) firstline = False if arg == 'data_files': # the data_files value is a pointlessly different structure # from the package_data value - data_files = data_files.items() + data_files = sorted(data_files.items()) in_cfg_value = data_files elif arg == 'cmdclass': cmdclass = {} @@ -532,7 +540,7 @@ def get_extension_modules(config): else: # Backwards compatibility for old syntax; don't use this though labels = section.split('=', 1) - labels = [l.strip() for l in labels] + labels = [label.strip() for label in labels] if (len(labels) == 2) and (labels[0] == 'extension'): ext_args = {} for field in EXTENSION_FIELDS: diff --git a/libs/common/pbr/version.py b/libs/common/pbr/version.py index 5eb217af..37c7a9f5 100644 --- a/libs/common/pbr/version.py +++ b/libs/common/pbr/version.py @@ -15,13 +15,24 @@ # under the License. """ -Utilities for consuming the version from pkg_resources. +Utilities for consuming the version from importlib-metadata. """ import itertools import operator import sys +# TODO(stephenfin): Remove this once we drop support for Python < 3.8 +if sys.version_info >= (3, 8): + from importlib import metadata as importlib_metadata + use_importlib = True +else: + try: + import importlib_metadata + use_importlib = True + except ImportError: + use_importlib = False + def _is_int(string): try: @@ -323,8 +334,8 @@ class SemanticVersion(object): version number of the component to preserve sorting. (Used for rpm support) """ - if ((self._prerelease_type or self._dev_count) - and pre_separator is None): + if ((self._prerelease_type or self._dev_count) and + pre_separator is None): segments = [self.decrement().brief_string()] pre_separator = "." else: @@ -431,12 +442,15 @@ class VersionInfo(object): """Obtain a version from pkg_resources or setup-time logic if missing. This will try to get the version of the package from the pkg_resources + This will try to get the version of the package from the record associated with the package, and if there is no such record + importlib_metadata record associated with the package, and if there falls back to the logic sdist would use. + + is no such record falls back to the logic sdist would use. """ - # Lazy import because pkg_resources is costly to import so defer until - # we absolutely need it. import pkg_resources + try: requirement = pkg_resources.Requirement.parse(self.package) provider = pkg_resources.get_provider(requirement) @@ -447,6 +461,25 @@ class VersionInfo(object): # installed into anything. Revert to setup-time logic. from pbr import packaging result_string = packaging.get_version(self.package) + + return SemanticVersion.from_pip_string(result_string) + + def _get_version_from_importlib_metadata(self): + """Obtain a version from importlib or setup-time logic if missing. + + This will try to get the version of the package from the + importlib_metadata record associated with the package, and if there + is no such record falls back to the logic sdist would use. + """ + try: + distribution = importlib_metadata.distribution(self.package) + result_string = distribution.version + except importlib_metadata.PackageNotFoundError: + # The most likely cause for this is running tests in a tree + # produced from a tarball where the package itself has not been + # installed into anything. Revert to setup-time logic. + from pbr import packaging + result_string = packaging.get_version(self.package) return SemanticVersion.from_pip_string(result_string) def release_string(self): @@ -459,7 +492,12 @@ class VersionInfo(object): def semantic_version(self): """Return the SemanticVersion object for this version.""" if self._semantic is None: - self._semantic = self._get_version_from_pkg_resources() + # TODO(damami): simplify this once Python 3.8 is the oldest + # we support + if use_importlib: + self._semantic = self._get_version_from_importlib_metadata() + else: + self._semantic = self._get_version_from_pkg_resources() return self._semantic def version_string(self): diff --git a/libs/common/pysrt/commands.py b/libs/common/pysrt/commands.py index 05bf78cf..b2bfa24f 100644 --- a/libs/common/pysrt/commands.py +++ b/libs/common/pysrt/commands.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # pylint: disable-all +from __future__ import print_function import os import re @@ -132,9 +133,14 @@ class SubRipShifter(object): def run(self, args): self.arguments = self.build_parser().parse_args(args) - if self.arguments.in_place: - self.create_backup() - self.arguments.action() + + if os.path.isfile(self.arguments.file): + if self.arguments.in_place: + self.create_backup() + self.arguments.action() + + else: + print('No such file', self.arguments.file) def parse_time(self, time_string): negative = time_string.startswith('-') diff --git a/libs/common/pysrt/srtfile.py b/libs/common/pysrt/srtfile.py index c03595af..bcdb482a 100644 --- a/libs/common/pysrt/srtfile.py +++ b/libs/common/pysrt/srtfile.py @@ -290,7 +290,7 @@ class SubRipFile(UserList, object): @classmethod def _open_unicode_file(cls, path, claimed_encoding=None): encoding = claimed_encoding or cls._detect_encoding(path) - source_file = codecs.open(path, 'rU', encoding=encoding) + source_file = codecs.open(path, 'r', encoding=encoding) # get rid of BOM if any possible_bom = CODECS_BOMS.get(encoding, None) diff --git a/libs/common/pytz/__init__.py b/libs/common/pytz/__init__.py index 6e923173..2e4dc394 100644 --- a/libs/common/pytz/__init__.py +++ b/libs/common/pytz/__init__.py @@ -16,14 +16,14 @@ from pytz.exceptions import AmbiguousTimeError from pytz.exceptions import InvalidTimeError from pytz.exceptions import NonExistentTimeError from pytz.exceptions import UnknownTimeZoneError -from pytz.lazy import LazyDict, LazyList, LazySet +from pytz.lazy import LazyDict, LazyList, LazySet # noqa from pytz.tzinfo import unpickler, BaseTzInfo from pytz.tzfile import build_tzinfo # The IANA (nee Olson) database is updated several times a year. -OLSON_VERSION = '2018g' -VERSION = '2018.7' # pip compatible version number. +OLSON_VERSION = '2022f' +VERSION = '2022.6' # pip compatible version number. __version__ = VERSION OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling @@ -34,7 +34,7 @@ __all__ = [ 'NonExistentTimeError', 'UnknownTimeZoneError', 'all_timezones', 'all_timezones_set', 'common_timezones', 'common_timezones_set', - 'BaseTzInfo', + 'BaseTzInfo', 'FixedOffset', ] @@ -86,7 +86,7 @@ def open_resource(name): """ name_parts = name.lstrip('/').split('/') for part in name_parts: - if part == os.path.pardir or os.path.sep in part: + if part == os.path.pardir or os.sep in part: raise ValueError('Bad path segment: %r' % part) zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) if zoneinfo_dir is not None: @@ -111,6 +111,13 @@ def open_resource(name): def resource_exists(name): """Return true if the given resource exists""" try: + if os.environ.get('PYTZ_SKIPEXISTSCHECK', ''): + # In "standard" distributions, we can assume that + # all the listed timezones are present. As an + # import-speed optimization, you can set the + # PYTZ_SKIPEXISTSCHECK flag to skip checking + # for the presence of the resource file on disk. + return True open_resource(name).close() return True except IOError: @@ -157,6 +164,9 @@ def timezone(zone): Unknown ''' + if zone is None: + raise UnknownTimeZoneError(None) + if zone.upper() == 'UTC': return utc @@ -166,9 +176,9 @@ def timezone(zone): # All valid timezones are ASCII raise UnknownTimeZoneError(zone) - zone = _unmunge_zone(zone) + zone = _case_insensitive_zone_lookup(_unmunge_zone(zone)) if zone not in _tzinfo_cache: - if zone in all_timezones_set: + if zone in all_timezones_set: # noqa fp = open_resource(zone) try: _tzinfo_cache[zone] = build_tzinfo(zone, fp) @@ -185,6 +195,17 @@ def _unmunge_zone(zone): return zone.replace('_plus_', '+').replace('_minus_', '-') +_all_timezones_lower_to_standard = None + + +def _case_insensitive_zone_lookup(zone): + """case-insensitively matching timezone, else return zone unchanged""" + global _all_timezones_lower_to_standard + if _all_timezones_lower_to_standard is None: + _all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones) # noqa + return _all_timezones_lower_to_standard.get(zone.lower()) or zone # noqa + + ZERO = datetime.timedelta(0) HOUR = datetime.timedelta(hours=1) @@ -249,8 +270,8 @@ def _UTC(): module global. These examples belong in the UTC class above, but it is obscured; or in - the README.txt, but we are not depending on Python 2.4 so integrating - the README.txt examples with the unit tests is not trivial. + the README.rst, but we are not depending on Python 2.4 so integrating + the README.rst examples with the unit tests is not trivial. >>> import datetime, pickle >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) @@ -272,6 +293,8 @@ def _UTC(): False """ return utc + + _UTC.__safe_for_unpickling__ = True @@ -282,6 +305,8 @@ def _p(*args): by shortening the path. """ return unpickler(*args) + + _p.__safe_for_unpickling__ = True @@ -330,7 +355,7 @@ class _CountryTimezoneDict(LazyDict): if line.startswith('#'): continue code, coordinates, zone = line.split(None, 4)[:3] - if zone not in all_timezones_set: + if zone not in all_timezones_set: # noqa continue try: data[code].append(zone) @@ -340,6 +365,7 @@ class _CountryTimezoneDict(LazyDict): finally: zone_tab.close() + country_timezones = _CountryTimezoneDict() @@ -363,6 +389,7 @@ class _CountryNameDict(LazyDict): finally: zone_tab.close() + country_names = _CountryNameDict() @@ -474,6 +501,7 @@ def FixedOffset(offset, _tzinfos={}): return info + FixedOffset.__safe_for_unpickling__ = True @@ -483,6 +511,7 @@ def _test(): import pytz return doctest.testmod(pytz) + if __name__ == '__main__': _test() all_timezones = \ @@ -661,6 +690,7 @@ all_timezones = \ 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', + 'America/Nuuk', 'America/Ojinaga', 'America/Panama', 'America/Pangnirtung', @@ -787,6 +817,7 @@ all_timezones = \ 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qatar', + 'Asia/Qostanay', 'Asia/Qyzylorda', 'Asia/Rangoon', 'Asia/Riyadh', @@ -933,6 +964,7 @@ all_timezones = \ 'Europe/Kaliningrad', 'Europe/Kiev', 'Europe/Kirov', + 'Europe/Kyiv', 'Europe/Lisbon', 'Europe/Ljubljana', 'Europe/London', @@ -1027,6 +1059,7 @@ all_timezones = \ 'Pacific/Guam', 'Pacific/Honolulu', 'Pacific/Johnston', + 'Pacific/Kanton', 'Pacific/Kiritimati', 'Pacific/Kosrae', 'Pacific/Kwajalein', @@ -1187,7 +1220,6 @@ common_timezones = \ 'America/Fort_Nelson', 'America/Fortaleza', 'America/Glace_Bay', - 'America/Godthab', 'America/Goose_Bay', 'America/Grand_Turk', 'America/Grenada', @@ -1235,12 +1267,12 @@ common_timezones = \ 'America/Montserrat', 'America/Nassau', 'America/New_York', - 'America/Nipigon', 'America/Nome', 'America/Noronha', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', + 'America/Nuuk', 'America/Ojinaga', 'America/Panama', 'America/Pangnirtung', @@ -1251,7 +1283,6 @@ common_timezones = \ 'America/Porto_Velho', 'America/Puerto_Rico', 'America/Punta_Arenas', - 'America/Rainy_River', 'America/Rankin_Inlet', 'America/Recife', 'America/Regina', @@ -1272,7 +1303,6 @@ common_timezones = \ 'America/Swift_Current', 'America/Tegucigalpa', 'America/Thule', - 'America/Thunder_Bay', 'America/Tijuana', 'America/Toronto', 'America/Tortola', @@ -1351,6 +1381,7 @@ common_timezones = \ 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qatar', + 'Asia/Qostanay', 'Asia/Qyzylorda', 'Asia/Riyadh', 'Asia/Sakhalin', @@ -1388,7 +1419,6 @@ common_timezones = \ 'Australia/Adelaide', 'Australia/Brisbane', 'Australia/Broken_Hill', - 'Australia/Currie', 'Australia/Darwin', 'Australia/Eucla', 'Australia/Hobart', @@ -1424,8 +1454,8 @@ common_timezones = \ 'Europe/Istanbul', 'Europe/Jersey', 'Europe/Kaliningrad', - 'Europe/Kiev', 'Europe/Kirov', + 'Europe/Kyiv', 'Europe/Lisbon', 'Europe/Ljubljana', 'Europe/London', @@ -1453,7 +1483,6 @@ common_timezones = \ 'Europe/Tallinn', 'Europe/Tirane', 'Europe/Ulyanovsk', - 'Europe/Uzhgorod', 'Europe/Vaduz', 'Europe/Vatican', 'Europe/Vienna', @@ -1461,7 +1490,6 @@ common_timezones = \ 'Europe/Volgograd', 'Europe/Warsaw', 'Europe/Zagreb', - 'Europe/Zaporozhye', 'Europe/Zurich', 'GMT', 'Indian/Antananarivo', @@ -1482,7 +1510,6 @@ common_timezones = \ 'Pacific/Chuuk', 'Pacific/Easter', 'Pacific/Efate', - 'Pacific/Enderbury', 'Pacific/Fakaofo', 'Pacific/Fiji', 'Pacific/Funafuti', @@ -1491,6 +1518,7 @@ common_timezones = \ 'Pacific/Guadalcanal', 'Pacific/Guam', 'Pacific/Honolulu', + 'Pacific/Kanton', 'Pacific/Kiritimati', 'Pacific/Kosrae', 'Pacific/Kwajalein', diff --git a/libs/common/pytz/exceptions.py b/libs/common/pytz/exceptions.py index 18df33e8..4b20bde9 100644 --- a/libs/common/pytz/exceptions.py +++ b/libs/common/pytz/exceptions.py @@ -8,7 +8,11 @@ __all__ = [ ] -class UnknownTimeZoneError(KeyError): +class Error(Exception): + '''Base class for all exceptions raised by the pytz library''' + + +class UnknownTimeZoneError(KeyError, Error): '''Exception raised when pytz is passed an unknown timezone. >>> isinstance(UnknownTimeZoneError(), LookupError) @@ -20,11 +24,18 @@ class UnknownTimeZoneError(KeyError): >>> isinstance(UnknownTimeZoneError(), KeyError) True + + And also a subclass of pytz.exceptions.Error, as are other pytz + exceptions. + + >>> isinstance(UnknownTimeZoneError(), Error) + True + ''' pass -class InvalidTimeError(Exception): +class InvalidTimeError(Error): '''Base class for invalid time exceptions.''' diff --git a/libs/common/pytz/tzfile.py b/libs/common/pytz/tzfile.py index 25117f32..99e74489 100644 --- a/libs/common/pytz/tzfile.py +++ b/libs/common/pytz/tzfile.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python ''' $Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ ''' diff --git a/libs/common/pytz/zoneinfo/Africa/Abidjan b/libs/common/pytz/zoneinfo/Africa/Abidjan index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch delta 33 ecmbQkIE8V7I4c7POq9`tGgEchxZHh147dPg+Xc1& delta 42 mcmbQjIEQh9I4cta0|V1U8BG=-%>d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Accra b/libs/common/pytz/zoneinfo/Africa/Accra index eaaa818f839bb79a4141e2c657d09e315be1cc1e..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch literal 148 zcmWHE%1kq2zzZ0GvP?kCG3nVP561uh|5!kkv-tRiFt`J82nmM#2LhZ1aRE&;-~s@2 CSQl9U literal 828 zcmcK2J!lhQ9LMp0n=A%e7ZpU8ZiS*bf|;`DlqJOnOqUF$AOv)95TVc_Jm}O#I@CZ3 zIHaUT`;ri@X^pQ(Nv$?14+KFv)zYy@7Yl-NeZG&QQ-(g__#8JJ-0z!g?p{72|DA|^ z!o`W(i~GSBUfy50F|8N6e^mKmRmy8|vv_i#Ul#7F%J)n1YO-KnH;b~AyJD7$yLu&c zTdh9sm+I9grZ%3`wZ9EjzuDCF{gm>Deu^JFFpbENZj?%D{cub+o_;fLw)Ui%&zZNC z0sSuhOue7EB_D9j$Ss7@OeiHihAA|s)Z*hp|BI-XhhnEelUu~Ury BmV^KR diff --git a/libs/common/pytz/zoneinfo/Africa/Addis_Ababa b/libs/common/pytz/zoneinfo/Africa/Addis_Ababa index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4Jhjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4Jhjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4Jd^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Bangui b/libs/common/pytz/zoneinfo/Africa/Bangui index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Blantyre b/libs/common/pytz/zoneinfo/Africa/Blantyre index 31cfad771a5c7c609e495da650e3ffbcf07c974d..52753c0f87bbfa457ada89d400908a3d6537ac0e 100644 GIT binary patch delta 34 fcmbQsIF)gNI4c7POq9`tGgEchxttwCbd9(GYuE+Y delta 43 ncmbQrIG1sPI4cta0|V1U8BG=-%>d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Brazzaville b/libs/common/pytz/zoneinfo/Africa/Brazzaville index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Cairo b/libs/common/pytz/zoneinfo/Africa/Cairo index 0272fa1ba0a09ae8380be83cc8838d97d0aa6d25..d3f819623fc9ef90d327380fad15341ec1a0e202 100644 GIT binary patch delta 29 icmZ3@znFi5I3vSGi5*NJdh;8mEiCL@uC5`vMqB`bi3jlj delta 38 ocmZ3?znXu7I3vqOi5*NV3_t+lY<|JCg@qf$<8pNk(KX@%0I&@Q{Qv*} diff --git a/libs/common/pytz/zoneinfo/Africa/Casablanca b/libs/common/pytz/zoneinfo/Africa/Casablanca index 04d16090dc9af7d28246fd6a855eb902c5d46559..17e0d1b89f093e1e4452a8921ffff9a91287eb4c 100644 GIT binary patch literal 2429 zcmcJQZA{f=7{(6@js%r8B1c<28Kg-Lahh2PIb5j$fd}w_h{lYUfN?xIBExYAK?j5~ zN2HwLok)yOU`q0qqQ~mZN$5l{B`ll2!c#XC?l+xCu56QT8 z9~rBkC*$y2Dl{OB!k&(zu=EiXR%K7&FR!KWNnhG#JV@IzPb7w*`%V>Z39<{%( zh%zics*JDKt2uTvUUO&6Gxt2twV&l{divpANU+ew&K@*4;Lggi_w5D1Ath{V(~fe?zRrQ$$ZET)zVgkX3e83@rp$Ob|< z5YmAV4-e!6As|yr2tq`rmJx)IAfyB#CI~q}2ns?{rWTc{Wd$KDQ%eg%T&9*6guqNK zF;j~SLS_&`Gqu!AEj9?bnOblVk~6jFAY^B1;Xz2x)Z#O>{2&Uz)Fl8>1g0(nh(a)R zDL@p1smlSPAWU5n5Jh3?vVbTIQhggoAXAqRL=l;~j35dLqLd(t38I`J3d+%1*IdfV!5cW9}u?jp?SWV0>$(b7;t}y4GkevCxe-Y;V zWXW0J`~lAe{*v?XPfsfBkGMrwWzbJi-UQFZUa#|9 z93wf8+1=suW9ua6@xff)m&}u#w(?1yZ9hoP(sc2?^qk}*qxemklJlfG$M;W~Bxl)V zB>(+Wo*8^z-rB))`D4jhQIf*v6}^(P(&EPZN|Kyaepx(MrAp2-ZeD!5K0lu%IUAhH_-nqf|hukISt;RIo-x`&i!#VeO f9=^Eo|Ne2C?T_GpyEprv(D`rZA5-5IKJ)(u0)S^{ delta 182 zcmew>bdr67xF8Dy0|N+yfCUhazyQJ^PzS^u6EzGr#++lUXJ%r7LS`l?WMyNg5B&fCwXcCwpF38TnzyQJ^P!GiH6EzH&H$9xVG5kDZJtGq{1Tr$gAqy)Tec=E90>4?n z?qXm7(tHAp9Fuo3>GLu$FfuSQf+ZLkvH4)K0&@b$3!AH%XEP5d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Dakar b/libs/common/pytz/zoneinfo/Africa/Dakar index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch delta 33 ecmbQkIE8V7I4c7POq9`tGgEchxZHh147dPg+Xc1& delta 42 mcmbQjIEQh9I4cta0|V1U8BG=-%>d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Dar_es_Salaam b/libs/common/pytz/zoneinfo/Africa/Dar_es_Salaam index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4Jhjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JHhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#VxM4$sA zZjO<1!_OqfhyB$Cr%$6UV zF+Hp8+aC`$wtqNke|7SbGj{hKdt7a&Gk!zm>}zhK1iv0ih&ZkivYIHd@O?@gzeYQJ z>S>4VZ8C2>NaohZ$UOP7vcx7)(nD#KR5+!QTD&Rw>5Y`U5J@}DhiPZ=XUh7ZMOnub zrKr1GgL?Z1swUeXmcjcJ2ssm+22RiyyzGVgFSJ3g8b z@QaPJ7-8!pj8J~BH!T0`K)3!lJFuuPc!2MN3ry_;E;s?g3lMIA@B@S+AUt7eS3vl} z)XsqL2825x`~l$*2#-Lx#MC}9wNoIxVrsWQ_{G$Yf$)r}U1MtBKsX1&JEnGzsr>`t zAX9q?!bPU`5rmUW?Ij2|nc7dLb`*rCOzkQNUzyrj5Z*GiyCD2!YKK91%+xM}@R_Nd z2H`bRyA8r`5RQZJoT*(0;X6}155jw;S-rtS=g-hk*1i2i`+5QrWzb(cW&iK#mU7H=yhrc-j3s4#`OBwliUFeAQ{ zhDgq`zAc3PviVE}9&xo2b5L@YXD2Jn17Nvq^+SV-mX|71-OuQ&F6#dG65$=Otw z$LmcuC1=8~)(+vo^`u>EFQTJ2y$rt_c&*-6qM|({_%}_e@L93uUkK{)G<7 z*=ru+xwk@cUS22v3zw}Q^ZFMHQ~dca!zE|m@DX0`_mP}e8pC=2ibrw|INUrBG)vB_ z+qUxgt4_%|^yp8#KNK%Hzn-!3JRBl9ulE^z{`!0fuaCHLc^(;%oSy7eyzV(6IY%SU z^Zuw!a*hQ&#q(ISb14BDqLoNU`bsCZY diff --git a/libs/common/pytz/zoneinfo/Africa/Freetown b/libs/common/pytz/zoneinfo/Africa/Freetown index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch delta 33 ecmbQkIE8V7I4c7POq9`tGgEchxZHh147dPg+Xc1& delta 42 mcmbQjIEQh9I4cta0|V1U8BG=-%>d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Gaborone b/libs/common/pytz/zoneinfo/Africa/Gaborone index 31cfad771a5c7c609e495da650e3ffbcf07c974d..52753c0f87bbfa457ada89d400908a3d6537ac0e 100644 GIT binary patch delta 34 fcmbQsIF)gNI4c7POq9`tGgEchxttwCbd9(GYuE+Y delta 43 ncmbQrIG1sPI4cta0|V1U8BG=-%>d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Harare b/libs/common/pytz/zoneinfo/Africa/Harare index 31cfad771a5c7c609e495da650e3ffbcf07c974d..52753c0f87bbfa457ada89d400908a3d6537ac0e 100644 GIT binary patch delta 34 fcmbQsIF)gNI4c7POq9`tGgEchxttwCbd9(GYuE+Y delta 43 ncmbQrIG1sPI4cta0|V1U8BG=-%>d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Johannesburg b/libs/common/pytz/zoneinfo/Africa/Johannesburg index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..b1c425daced454f53d7d18fea807bf8d081cf97e 100644 GIT binary patch delta 35 gcmZo;`o=gxoRt9tCd&B3nf>V;T)~dPA-YCf0E4*)5dZ)H delta 52 pcmeyy*v2$LoRx)vfq`YBj6VmE1_QWQd%6Hjj4RkNI7HWo3johQ2Ppsm diff --git a/libs/common/pytz/zoneinfo/Africa/Juba b/libs/common/pytz/zoneinfo/Africa/Juba index 83eca03ab87f54441e6fbff4cfc08408c4e77c16..06482943a45a58a02a43b9e2b6a3f215b21b045f 100644 GIT binary patch delta 209 zcmbQsx}0@_xF7=(PzJJDCu;PtB#27|Ob%oco}9qM0>j3POw3FS46;i=8ng@;Sr`~( x6c{)`YzAHk+s8MA!PzkwM1(N70x>r8H|sNsFmiwl!DfpU8Mbf%thjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JSHR1vQkEI6@ delta 57 tcmZ3^x|?-^I4dgy0|Vdo7EVr7zJUX3=CY(jv=~6Tmbe}2QL5s diff --git a/libs/common/pytz/zoneinfo/Africa/Kigali b/libs/common/pytz/zoneinfo/Africa/Kigali index 31cfad771a5c7c609e495da650e3ffbcf07c974d..52753c0f87bbfa457ada89d400908a3d6537ac0e 100644 GIT binary patch delta 34 fcmbQsIF)gNI4c7POq9`tGgEchxttwCbd9(GYuE+Y delta 43 ncmbQrIG1sPI4cta0|V1U8BG=-%>d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Kinshasa b/libs/common/pytz/zoneinfo/Africa/Kinshasa index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Luanda b/libs/common/pytz/zoneinfo/Africa/Luanda index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Lusaka b/libs/common/pytz/zoneinfo/Africa/Lusaka index 31cfad771a5c7c609e495da650e3ffbcf07c974d..52753c0f87bbfa457ada89d400908a3d6537ac0e 100644 GIT binary patch delta 34 fcmbQsIF)gNI4c7POq9`tGgEchxttwCbd9(GYuE+Y delta 43 ncmbQrIG1sPI4cta0|V1U8BG=-%>d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Malabo b/libs/common/pytz/zoneinfo/Africa/Malabo index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-klT+WUmx<*_8jK>A! diff --git a/libs/common/pytz/zoneinfo/Africa/Maseru b/libs/common/pytz/zoneinfo/Africa/Maseru index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..b1c425daced454f53d7d18fea807bf8d081cf97e 100644 GIT binary patch delta 35 gcmZo;`o=gxoRt9tCd&B3nf>V;T)~dPA-YCf0E4*)5dZ)H delta 52 pcmeyy*v2$LoRx)vfq`YBj6VmE1_QWQd%6Hjj4RkNI7HWo3johQ2Ppsm diff --git a/libs/common/pytz/zoneinfo/Africa/Mbabane b/libs/common/pytz/zoneinfo/Africa/Mbabane index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..b1c425daced454f53d7d18fea807bf8d081cf97e 100644 GIT binary patch delta 35 gcmZo;`o=gxoRt9tCd&B3nf>V;T)~dPA-YCf0E4*)5dZ)H delta 52 pcmeyy*v2$LoRx)vfq`YBj6VmE1_QWQd%6Hjj4RkNI7HWo3johQ2Ppsm diff --git a/libs/common/pytz/zoneinfo/Africa/Mogadishu b/libs/common/pytz/zoneinfo/Africa/Mogadishu index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J_<(VOI4cVS0|U!M8D|b44F+(rYJWbM7?-#jXaZ diff --git a/libs/common/pytz/zoneinfo/Africa/Nairobi b/libs/common/pytz/zoneinfo/Africa/Nairobi index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J-xLo0mA-aZK0NTq4!~g&Q diff --git a/libs/common/pytz/zoneinfo/Africa/Niamey b/libs/common/pytz/zoneinfo/Africa/Niamey index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Ouagadougou b/libs/common/pytz/zoneinfo/Africa/Ouagadougou index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch delta 33 ecmbQkIE8V7I4c7POq9`tGgEchxZHh147dPg+Xc1& delta 42 mcmbQjIEQh9I4cta0|V1U8BG=-%>d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Porto-Novo b/libs/common/pytz/zoneinfo/Africa/Porto-Novo index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..afb6a4a8fb17b0d4670b8ea1b38f5cc6100244e4 100644 GIT binary patch literal 235 zcmWHE%1kq2zzbM_vLGzfwz}YAPe200v{lX*7Y4qsU}RuoW?*2}hw28ZVdr4rU|`@A xVBqud4PkHxVr>HhV*`e8#}I}P5^VYp1R&c$G{{B}4YCzPlWsE?(0W}%E&#d^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Africa/Tripoli b/libs/common/pytz/zoneinfo/Africa/Tripoli index bd885315f84f8615da866553c9c0086ad430ad01..07b393bb7db14cef1e906ebe63cfbbe8cddc79d5 100644 GIT binary patch delta 36 jcmZo<{m3#woRt9tCd%A|F*i#xMl!N0z`*6~8lr2+1pv7)2TA|{ diff --git a/libs/common/pytz/zoneinfo/Africa/Windhoek b/libs/common/pytz/zoneinfo/Africa/Windhoek index 6766185683f45d5998f2b84d0c384067a46e19dd..abecd137b1fc3220637b22ffea0e7256a58e9377 100644 GIT binary patch delta 37 kcmcc2zMFl5I4c7PY?KLMgt0fzVANn@=W=!o(KX@%0Ff>R-v9sr delta 62 ucmdnZewlrOI4c_i0|VPenGi-EAOj9y5}SJ%HJF6q!axR>vtx*^5f=akFb4ww diff --git a/libs/common/pytz/zoneinfo/America/Anguilla b/libs/common/pytz/zoneinfo/America/Anguilla index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dYl`jj2 delta 104 zcmaFH*1$eNoR^t_fdPa;z+|Gz6_)z{|Nl=GWY(Xoz|1n)o>`s^B8RSYb1vfoMh=i1 Rx&}Uo1}+<217ka5E&x*U6bt|W diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Buenos_Aires b/libs/common/pytz/zoneinfo/America/Argentina/Buenos_Aires index dfebfb99abb86536da12b96677dc0c76c088cab8..cc82e69898f161842b5001f10a84d3ac127c5c6f 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Catamarca b/libs/common/pytz/zoneinfo/America/Argentina/Catamarca index b798105e0f660c7b85a663d945d9eb7f04505e52..7268eb3738e5b3ba906f962533f48016cf3dcabc 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/ComodRivadavia b/libs/common/pytz/zoneinfo/America/Argentina/ComodRivadavia index b798105e0f660c7b85a663d945d9eb7f04505e52..7268eb3738e5b3ba906f962533f48016cf3dcabc 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Cordoba b/libs/common/pytz/zoneinfo/America/Argentina/Cordoba index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..2ad6ea5db204d599266412558f5a1fae076fc41a 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Jujuy b/libs/common/pytz/zoneinfo/America/Argentina/Jujuy index 7d2ba91c679aca41eb44aec6115745ab107531e8..7ca0b46f680b80780981aab0699e64dd04070fe7 100644 GIT binary patch delta 89 zcmdnM(Zw-AoR!pJyTkx_NB0VB&~H%7t95sVh-iZ&l$e87mQjDyQY*TC4$ GmiR%BG1Y{1Adc_*{r diff --git a/libs/common/pytz/zoneinfo/America/Argentina/La_Rioja b/libs/common/pytz/zoneinfo/America/Argentina/La_Rioja index 7654aebf0b084f081db62e2b03dfc287df974e35..a6a6694f3317a25e7bc4c66502e9f396fa106509 100644 GIT binary patch delta 89 zcmcb`v4vxTI4=Vduv@6Y$jCU^kWq2610%=epUfhY`B*H_6>UDx_ydbF4lWyA17ka5 FE&$9d5T*bC delta 126 zcmdnOaf@SuI4>Im0|N+yfc-)hM%McO|Nk>iKFBOT*@2O3@&jg($%%}XJP-vC5?$Bk ZeT+XCIYDw*^@L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Rio_Gallegos b/libs/common/pytz/zoneinfo/America/Argentina/Rio_Gallegos index 3c849fce2f09e040563440b93d1eae975eee4eea..8b1a2816ab136098d10a3d294993ea56b6c432dc 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Salta b/libs/common/pytz/zoneinfo/America/Argentina/Salta index a4b71c1ff07647569d5284a3bf4832dccc9fae9c..7072dec22958ad5ac74ba3457a178a4faadaf570 100644 GIT binary patch delta 89 zcmdnM(Zw-AoR!pJyTkx_NB0VB&~H%7t95sVh-iZ&l$e87mQjDyQY*TC4$ GmiR%BG1Y{1Adc_*{r diff --git a/libs/common/pytz/zoneinfo/America/Argentina/San_Juan b/libs/common/pytz/zoneinfo/America/Argentina/San_Juan index 948a39010420a9981fef631cae134cdcfe844047..f3e185c3ab516065c52557aaceaaeacae1f8b221 100644 GIT binary patch delta 89 zcmcb`v4vxTI4=Vduv@6Y$jCU^kWq2610%=epUfhY`B*H_6>UDx_ydbF4lWyA17ka5 FE&$9d5T*bC delta 126 zcmdnOaf@SuI4>Im0|N+yfc-)hM%McO|Nk>iKFBOT*@2O3@&jg($%%}XJP-vC5?$Bk ZeT+XCIYDw*^@5O*hiZ)+o{DDOo2bYbmfw7%2 F7XZG05HJ7$ delta 130 zcmX@W@rq-DI4?T`0|N+yfc-)hM%McO|Nk>iKFF*(*@2N|vLBIm0|N+yfWt-=7Dm?k|Ns9pPBvszob15JG5IaC=wwC~OCE>{2#Kz3 Y@nJ?LPLLEV!Z diff --git a/libs/common/pytz/zoneinfo/America/Argentina/Ushuaia b/libs/common/pytz/zoneinfo/America/Argentina/Ushuaia index 1fc3256773606e80a3d300ddcbfffafe72b56e45..e74ce049c793c96e855ebb15779887a48622bf26 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Aruba b/libs/common/pytz/zoneinfo/America/Aruba index d3b318d2d67190354d7d47fc691218577042457f..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dIm0|N+yK+{GQ9wwIh|Ns9_HehO;yn)G-2O(N%BeWENoI0LdY1 M;YQLj`7FC405!T3{{R30 diff --git a/libs/common/pytz/zoneinfo/America/Atikokan b/libs/common/pytz/zoneinfo/America/Atikokan index 629ed4231944255aaa0725f807517df48b39e135..9964b9a33452f4b636f43703b7cdec4891cbda5f 100644 GIT binary patch literal 182 zcmWHE%1kq2zzdjwvdlot(*Pv8za+k3WcvSqYXJiTkd$Cx`Tu|C1_llv-w+08Aa)H7 ZVF)3?%>O`;*{&u4qKPq^3uu8U7XX0%EG7T| literal 336 zcmWHE%1kq2zyNGO5fBCeb|40^B^rRlyd4W0=I{DhaN z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 diff --git a/libs/common/pytz/zoneinfo/America/Bahia b/libs/common/pytz/zoneinfo/America/Bahia index 143eafc2c0ab9d7f54bb0a21649d32ed1b4489d1..0b65e49fca21f1be92c3ab0dfa4cd05137f028b2 100644 GIT binary patch delta 77 zcmeC-_{2UzoR($ZXBT$TT^IQJxJVg|2e*e8vxq93VL~ Q^?VTZTsFD}#&*VB0A!jJ$N&HU diff --git a/libs/common/pytz/zoneinfo/America/Bahia_Banderas b/libs/common/pytz/zoneinfo/America/Bahia_Banderas index cd531078d0e8a4752bc242e83836dfdf0ae307ca..ae4a8a754617b8b918daffb6a45a067df2fd2fe6 100644 GIT binary patch delta 322 zcmZ3+)4(}FT#$o-fdPa;z#fP>7HTjiF*30*voNu+0!iGEjR_b0|9@@>0|Stpz`*kV z|LO${93YaB2PV?rz%cnXtE7ytO9(?ia0r7lkY->6I{{8(bK2%ora$y@oDx3AaXANv Hm~jCBl-)Sn delta 755 zcmbu6O(=tL9LInE$JSV@AupvI+^BhMH9JU3lM9c;T$H5P%$R3h*1SIdQp&|k&WhA1 zC35m|wX5P}<)$3{IZ5>Rf1eyE9OnD)*XL>N^!f0Dv}?V*$U#IntWTa}rgEERU!6^zp<6*OyoHMDHz4gk$<- z!#Pd0YWj4=7A5PVBKc}i%H1YX*#Vk)tQE6oL7KaAi}~GtzHs;?7MGg2v3etzV!fU` zX8YsWm7LyYm94aKjg!kX54KsMX`UXj5`0sByA~p?J48Wwo$|C6`7Q7JezJA)z}}dF z|G^msZy?-(@Mlpv1mO{cOAtOmI0fMqgj*1PK{y8C8H8(0`ZlZ|ARU8v5b`kuuqY#7 z2w_pizz_sP6bxZN!~qcqL?rM_sLxIvzr@NZlVV4x(0@WXWygT*=h6JCPxX2Dp9G_D!D|Vm-rVtlJl_*^@e}o z53GHCR4Rqpyz^|PnrC>c-z(BrMfC(hC3Wz3=9P6fyoF*POy%K{7srV&Ps^1M-9g8u zyR?IuNG?dGMnjPvU!}tvBGXo#tXpoF%wUFPM9kMyYL_EY>MWS0)OJI$Pe1Nh;x(`P zoT@dQCZ~eyc`gxS#NM6%u(;BN=!BH delta 98 zcmdnQa)xDsI4?5;0|N+yfZRlt8kYM1|Nl?^$SBRk$TV?}JR3v?L*f4q93UA~#e5LO MTsFD}#&*VB05PH!a{vGU diff --git a/libs/common/pytz/zoneinfo/America/Belize b/libs/common/pytz/zoneinfo/America/Belize index fd6932140d6795c404dc09d0f93cf789bc484870..e6f5dfa6a8d82523e8cb12f17cf73560bc09a383 100644 GIT binary patch delta 778 zcmb8tPe>F|0LSqe$8AxhLr_|tN+JpaOSHO3wVp(2sRduRx9hD-s}Olx51vFJ9Xc2! z*tJXOX;DH!gCL^jAWzrynaZ|WX}0)3*7tp0y)^LVGc&jS{&p(-x$H&x*;9_=aJUlY z@O+`adOCIYk`Ai!$>2kKbmr>5oSAJ>s$^cObhVnzy_a)?r&T7H)AOC1>f6Y;UJ!v= z92nC2-iBD}=+fUWeih3P6MDrR6{|NJ_3F|~k*$o&?BsLt;~*k)!;i&U=CWMxd8U4j zh2^g&t?KuyoZM)-qc*!ky7tetO10IN)qhL=seI(4&gT}@cGX+G9UO{XuTSreEQr13 zr+R;2LL7YRm4_W~`giUZdBuL*yW%IkVlPw_@*CXqHwuOHJNJyepKET3IoE6IYHOUx zqnHzEH79CLV~pSLxW{)t=~kQ^X7sh&=aQyiF7jASA|#X5q(X98O)?}Kk`Bp-Bt$YI zDUqB=QY0&;Xv+hAk;X`8t7(n&wwmThcceYiAK3ue0oelC1K9-G#gHnn4IkJC*$COm zYPLf5vYO41-K=IiWItp>WJfex3Z@7rGSZs<62*#myLtB?c(+d2`SySB?39{6L4D~F D-^9rM delta 166 zcmX@dbA)|@xF`z)0|N+yfH@F@*g_L^js5+2qBHPOOYf%uLLa|FTL-`S^w~ y=o*+B8!$KrhcGz1gn+bx0WPC9_b~M`asdtd4+J0st675(1}fk-kPB#o85aQAEFtXx diff --git a/libs/common/pytz/zoneinfo/America/Blanc-Sablon b/libs/common/pytz/zoneinfo/America/Blanc-Sablon index f9f13a1679fa9ca7cee6a41bf094a861d4c6824a..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-d$rf9HsJyQxN&4H diff --git a/libs/common/pytz/zoneinfo/America/Boa_Vista b/libs/common/pytz/zoneinfo/America/Boa_Vista index 69e17a00e161ef17bb763868db7793b609a39556..08d518b151425f85458727fbf69f6e601f12be86 100644 GIT binary patch delta 81 zcmZo+eZ?|CoR?3|Nkc&G6_$1U}Aw`CPt=-7v$L>s?arU7G@M+ VAY=H(KF(V?hK#RFJKzm=TL;e5%{~4K?m?zq6asZ{k fV1hU=ir$Gm8ayBgB((w%l?)7AHo69;cBWhag)|e5 diff --git a/libs/common/pytz/zoneinfo/America/Boise b/libs/common/pytz/zoneinfo/America/Boise index f8d54e27479149d35f6ddff12f709096f08bfac3..aad1d991c49c2e0fa28be3da4de447af33c8df1c 100644 GIT binary patch delta 429 zcmca5^h#)gxF81u0|N+yz;qzy*r<`fRL{)F#K^?P!pg=zAW#dJwg3N5=4W8||9|cT qMwb8o`x_WICTla>axyY7f<3@EIiFb?(@UFYGw)#;x_+|blmP&We8wmRL{f+gsjXgtZV}URd8AR|Nmru28RFt=T2Z` inQX{x%gw+Db~%W`=9$flnfI^^8Q-u&eKXmQQw9LlG#*+2 diff --git a/libs/common/pytz/zoneinfo/America/Buenos_Aires b/libs/common/pytz/zoneinfo/America/Buenos_Aires index dfebfb99abb86536da12b96677dc0c76c088cab8..cc82e69898f161842b5001f10a84d3ac127c5c6f 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Campo_Grande b/libs/common/pytz/zoneinfo/America/Campo_Grande index 495ef4568347ab6d3887a2cdd762c960f567a0ca..53b3330fac2bd64c02d8dc171c722ec491bea41d 100644 GIT binary patch delta 70 wcmcb_KaG2WI4=Vdh~B7D!Z_K0DSC1ryBxaQ<}XYSm|&6|TsFD}CUz!V0L}6Zod5s; delta 581 zcmY+=O-lkn0LJlc!wyDNL}-^N2rrq<`T`*#b1Mijs8eJwiio}Wf{3lYim{%1s6W0+Ca9+2wzX~ zxp7A3kKBAgyJL%6AsXpCVbQooqd&JKHr7RB4;dDBeA0Mrnk8DEY2qMFmOd`&vOYyt z?w|PTjE}6Hp7W&NMpE1RJl)eEnPh=y8m}Z93iE9JisVLgn!Bm9ynUADkH5(JcYx}Z zYBwvq^wEuo!8R{cTAVl}#@;(M`z;}pSTr+b>9xq0H9J)MzqhZP`P4Vnz(Y)cm;o^b zVh+S4h*=QRq+%Y#M5&kwF;yz&LQIy5*$~sAm~Y4fG{6BmK(v790nr4a3q%{K=mXJ6 zDmp>5l8Rms&7`6mL_4Tg{8jk}G{gZqLbQbF3DFdm28;5(TvqsBV-FfT@K8~#qeH|S Y@QQCwz!}sum*yhF#2IwEJosV#1%Q*I`Tzg` diff --git a/libs/common/pytz/zoneinfo/America/Cancun b/libs/common/pytz/zoneinfo/America/Cancun index de6930cd8ace99cd0a9658951c64b98b71a5f389..e7acbff18a2469c79f4ce77064adeb189d60bf10 100644 GIT binary patch delta 238 zcmZ3)c8G0)xF9mq=InOl?9AU%uEySYEKa71xmsJ U>%;{HEKn&9E*o6~6FUg4*g_3JVzQi$&9@nEJlvxv6@;sL7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Cayenne b/libs/common/pytz/zoneinfo/America/Cayenne index ff59657820c61230c738875e72036cd133326f41..e898594276143fa4cfd5d3815c4598b9e58dba07 100644 GIT binary patch literal 184 zcmWHE%1kq2zzdjwvdlotGx3Y|gx`lv4=^(Q|9|cS1H=FS#}6>D{QrOT0t1JSZwP~~ dfe8>BGlY;}=6@gnnG2$cF`LTW=+1sD+2%_Qx^~b delta 139 zcmeB>`5`?)T#%iCfdPa;;0+M7Z`9brJXw%Mm5-T;5e`{cfkN9SA7c@l{D{Snn}LB5 atN}=2Gh(v`YYIC7Gk|u2&6xa#R|Wu-P87-j diff --git a/libs/common/pytz/zoneinfo/America/Chihuahua b/libs/common/pytz/zoneinfo/America/Chihuahua index b2687241cd05b6904a7d95bae697642becbe5b1a..e0910396704ffda1d7f39e70a369249aeb4b0353 100644 GIT binary patch delta 346 zcmaFDeU4*-xF81u0|N+yfDI6HOw{;N&&b5W%)-P9gv>w&Ns#IP|2aJj4FCVnoxs5I z|NrU*3>+Ymkq0KSa|5FQOavtB;~T=@8yv#m48*=JK-wjQn}LB5>>waLS%*m$(~+B_ Vn4T~Wv@^w#oC$F*7tj-CTmX!=NlpL& literal 1508 zcmd6mZD`F=9LIl`d1$R1A|Vn=wC*142{UHbX6$Y@cQbd`-OOgpHpaRhcZyat97@y- zCGwEAl$odUN+^UBlUkB3DI$4Ff9Lb}MtP;Y@I9SAr~jM(o6jc@tgeigKQ_>Q;j*B; zyoaCa?5l78P}|d4SrodQ@hH?4l@#jw(fmHHs!`$_2G>u^Dwc$TH}#2==c}anZk;@6 zs!AF2SyFnV)b#JqWX7jqW@hI@N$rRg=ZtYQd`kdSO?g%DFc|{6~^i?u7`+3y)HZT3$^`PS55?+GwN_<2{8FA>u2!yyk%}okwJL3>R1OQ7s<9uX>cd>K`o|Ngel*Ffd2w0R zUL0s@ZavoP4)mGY)0cGJ`n#sS`LJFe>{S~oH|vHOchttr61{2kdDR#bmd0;;Rd`5H z!rgUh^Se~pay4IVeK1b8om^+Ow~Or9nP-~zy%bMBPJeqMJpcAM&e0YT$7wSX;W+0{ z$Y960(&35X?NPtJ=lBESUmTDOuFt;h{e6MIFvhXN8i+X%dmsiuEOKd0;y>8LFbZN7 z!z_qh5W^srK}>ULZG#xcunuA#!#;?C3=1J9GHiqx39%AlCd5vNp%6O`;*{&u4qKPq^3uu8U7XX0%EG7T| literal 336 zcmWHE%1kq2zyNGO5fBCeb|40^B^rRlyd4W0=I{DhaN z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 diff --git a/libs/common/pytz/zoneinfo/America/Cordoba b/libs/common/pytz/zoneinfo/America/Cordoba index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..2ad6ea5db204d599266412558f5a1fae076fc41a 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Costa_Rica b/libs/common/pytz/zoneinfo/America/Costa_Rica index 525a67ea7913b5a9e2507067c164de30259d0fbb..37cb85e4dbfb7ac9c01eecf584a1a721ed251e93 100644 GIT binary patch delta 33 fcmX@Zw1;VeI4c7POq9unGgnV$<8lrTG2;RNd%Xt+ delta 50 ncmdnPbcShyI4cVS0|U!MnQRUq4F+(r#gqA9VqDI_A!b|v%Kir) diff --git a/libs/common/pytz/zoneinfo/America/Creston b/libs/common/pytz/zoneinfo/America/Creston index 0fba741732f73cf241e702d41f4ccb9be60124d3..ab37e845566aa95659b7b85be0051d0c67a7e53a 100644 GIT binary patch literal 360 zcmWHE%1kq2zyPd35fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O1$i&FR41|nK|Nl>W$H4Ia|LO&dEdT${oxs2WX7fPUKE5Fg zzAiu<48-9fKr0v+7{Npc2~PVD1aT}p8$dM3i69!}Ob`unDu@O-7es@c45C5K2Gc;N WgJ_WRK{Ut}%cy^L|=0FfuXz|3B#n1H=FSb0;vc{QuwI sz`y}v`}l@1_y&hC1OPD%gpgp(e;|mnE!YF1LDqq2GOXkRy1|?a0DglyQ2+n{ diff --git a/libs/common/pytz/zoneinfo/America/Cuiaba b/libs/common/pytz/zoneinfo/America/Cuiaba index 8a4ee7d08fcae58764f1b1c4e8ce19f548c4734d..26e97f6ebfc60c1517244840b77dbaedcbfabaaa 100644 GIT binary patch delta 123 zcmdnS|BGvaI4=Vdh}@`>!#LT1DN^qL|I;}P4FCUMy}-!w|Nrp=3>-ebAq=_(#z1Vs ZfUa%xE2axfAbo21^>Nt%b=#S60RXBsBw7Ff delta 580 zcmZ9|JxfAS0LJmFp@R?=5!Mn7;o?i@s)0zz*$P4oY>Mnf5wR>&MC@8#b!jYch$8y} zK^+Nt8Hz(|K{Z54YXp6OBAq(VsVU+5^PcN)pMTqO*0p2p>`)X17e1xNaLS-ZuBZ6i z1S9i@ZoZ)2vBj+rjrJU~SVE<-_AQBz_tN-dmL+VTG|`x5N%J#J?q|r-`z2l0!X$P7 z#8+lU$?C~DPy4N8ZF`Sr23jPWF7j;amE=MZo@-u_yicR~nQY@~ggAn3mj^#AKgqD9YybcN diff --git a/libs/common/pytz/zoneinfo/America/Curacao b/libs/common/pytz/zoneinfo/America/Curacao index d3b318d2d67190354d7d47fc691218577042457f..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dFGDP~{(ngvQ>GqA6a) zH6&V^LfVbSrf6yj_m;Bvxv8NJ?tU-a4flVZx~jV9^K>{I4sI?>-JHF*zKmuS^p)_d zTs^%IYvXmfp4b-~-4z)NE{a&QBoo6kBGHJ;Wc#2<-h|}ldrNFx#*FRf0evSQG*Y#1 zJ-sk$?4G^pdsA*BlYZ2*o~DtTtLnK{r_4{+MgH|e9`skl!9zn9K1<^8x+afa;^O$^ zTo&E;A#t*tmDbgmC`)arT-S!mUz)$%)8^IrTaAah@V_Bwec?dB448h`4?Y!~wEzGB diff --git a/libs/common/pytz/zoneinfo/America/Denver b/libs/common/pytz/zoneinfo/America/Denver index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..abb2b974a47eb3e5c8b4f5d4370baf4898b239ab 100644 GIT binary patch delta 145 zcmeAXo+CU#T#$`{fdPa;U>*>&ZPch?;$~)Kf*>&Zq%q@;$~umLS~l98<{3fw&N6=9K)>0&cFy#$B0ee n=5@?pSa4~Shic^V4GuB)bqUe&HP$oIGtluhG}JTHGvERMYMd1i diff --git a/libs/common/pytz/zoneinfo/America/Detroit b/libs/common/pytz/zoneinfo/America/Detroit index 5e0226057ac5e154901b0debb90547c096f9083a..e104faa46545ee873295cde34e1d46bccad8647c 100644 GIT binary patch delta 94 zcmew-uuX7+GGp&V6&3#9#uIvezp#Gr=Z4jXzb9ljx=&)7yn&r%vK)uUWJPA6qRFnz e4)Xtj0HhL3166})kPZ+H)U~;Vc@_&)TLJ*}_A>hb delta 38 tcmdlc_)lPhGGpgN6_t$<6PYHzWy)skoE*vQuz4r*Oco&5WAZ!>2>>384pjgE diff --git a/libs/common/pytz/zoneinfo/America/Dominica b/libs/common/pytz/zoneinfo/America/Dominica index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dfdPa;Kxv{%4@>?3|Nkc&GRd+qGBGnvyr9hmkpq$F$~Vh13NUhj UWY9GUfdnCd%SPA0)XtO(09YgvfdBvi diff --git a/libs/common/pytz/zoneinfo/America/El_Salvador b/libs/common/pytz/zoneinfo/America/El_Salvador index ac774e83f46bb2967f587ea3175d6d2d95a15387..e2f22304aad56062cfb66d23f3a8c296689286ed 100644 GIT binary patch delta 33 ecmaFE_<(VOI4c7POq6klGh3tCxSWGS%(wu4IR?f6 delta 46 ncmaFB_=a(UI4d&)0|WC!8Fw}y4FqtJ`e+`A2$yqkh#40Exs(Rj diff --git a/libs/common/pytz/zoneinfo/America/Ensenada b/libs/common/pytz/zoneinfo/America/Ensenada index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..63dfdf48a68d02240737ecd6af081e02eb0b6317 100644 GIT binary patch delta 551 zcmZ1`bWCW1xFiPy0|N+yz*Haxu_cyndr`2_Ac(1+nTdsol?_M?49vl1?f?Iid>9!1 z|DQX7f#v`I{ssmP7<=vnMjjBGQ2mv`lD%xFj0`0|N+yz*Haxu_cyn`_{10Ac(1+k%^g!g%wB)42;2M?f?Iid>9!1 z|DQX7f#v`I{ssmP5Sx()ObUQW5g*?W2H)Tih5!(D0pf5V4hR8xl7WE{LQW8u#p?P0 VKmZDW%?p_qu?*`Fn5@Ss0RTUKN9Oj|6gy#!0`Y7 z&JBz#|NpOEz`!wCoynG$k%19xJDA4iqRrEpWSHscBvlkAak&PEn7X=z==d7z8R;45 N_!=7O8R{8u0RTxGC_n%J delta 305 zcmbQl+r&FTT#%iCfdPa;AQ_0+H)>2^~hM7K2QATnKmuql{sjEwfj<2zvk)DB$uc4uyp`HO3 E0D)T}6aWAK diff --git a/libs/common/pytz/zoneinfo/America/Fortaleza b/libs/common/pytz/zoneinfo/America/Fortaleza index e637170a6edfe97e99e4c729ccfc35319e1fba85..bee1a95152cbc37db7117d996d370ff9d3f11f79 100644 GIT binary patch delta 75 zcmcb?x{q~&I4=VdP@kx>Wa0&7W=1B)$*Y;<(PcN=Fgh?I$#Zbo=o%Q?8FK*u`A`iG delta 94 zcmdnTdV_U>I4?5;0|N+yfW}0XB`o#-|Noy{$fQ2;oje;v7+uw7Lq-Qi4v-v@T0V$c ME*o6~V>@Fm0L!cs;{X5v diff --git a/libs/common/pytz/zoneinfo/America/Godthab b/libs/common/pytz/zoneinfo/America/Godthab index 0160308bf63690a6dee51c3c8849f494a47186c2..0d2149cc0033785b517624400583fb9edf2ff2ec 100644 GIT binary patch delta 53 wcmcb{cY<$%GGpmRl{<_~j4YEUu;nu|Gciy8&t^J#9TQMu^ChMY%&0Qq0J}~PyZ`_I delta 51 xcmX@Xca3j?GGp0Bl{<_q_5c6>pTtx;`6HX@HZXHA00BZO8~`r?6w~B9qI4=VdDBh@I!ZBqoi?1xyy`3O3(hN@3dU!rI0NS0cgX8XRKk X>Jp;kYpiFaXQ1P2XsBnXXTSvjolz5K delta 105 zcmZ3*cY$w$I4>Im0|N+yK*>fG6GoOW(^UzRBN$&XGEd&fmdFEw8VFGK| Stj^lS2+_ffq+_xmyCeYKo)MM+ diff --git a/libs/common/pytz/zoneinfo/America/Grenada b/libs/common/pytz/zoneinfo/America/Grenada index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dN;e=cYjK67AXW}0|b6D&D#fg&@6%fV%%YhY?; G$^`%u=Men> delta 116 zcmaFC*v2$LT$hD`fdPa;fE9>AY=H(Kv2BTygtpk93)+Rx9P0o7|If(G#5~bnlLIIP g1{1`2QS?si(cl3|AgL9AsAOQ^ve7j#wKL@c0PQ#wo0x(~AU1)R1tddAun}mj|6gy#!0`Y7 z&JBz#|NpOEz`!wCoynG$k%19xJDA4iqRrEpWSHscBvlkAak&PEn7X=z==d7z8R;45 N_!=7O8R{8u0RTxGC_n%J delta 305 zcmbQl+r&FTT#%iCfdPa;AQ_0+H)>2^~hM7K2QATnKmuql{sjEwfj<2zvk)DB$uc4uyp`HO3 E0D)T}6aWAK diff --git a/libs/common/pytz/zoneinfo/America/Indiana/Knox b/libs/common/pytz/zoneinfo/America/Indiana/Knox index fcd408d74df43310a9a85c475f83d545f6d75911..025d132dd48ba978c6fedf86d70173127be49d49 100644 GIT binary patch delta 152 zcmew()FV7WT#$o-fdPa;U=|Q_Y}CkMVrOM!Vq{`wnS78*o{??xM?G&X}aFJr#KLX=4yFq0-Ha!LaLn_U^_ delta 138 zcmeAX{v$L&T#%iCfdPa;U=|RwZ`8R delta 262 zcmX@byNh>%xF9OV diff --git a/libs/common/pytz/zoneinfo/America/Indiana/Petersburg b/libs/common/pytz/zoneinfo/America/Indiana/Petersburg index 0133548ecac014f4b37f0abcd471a5a6b4e7ed5f..3082de00c2e0bfe89d4ac5e093d9e5921aa63610 100644 GIT binary patch delta 316 zcmeys*T6qPT#$o-fdPa;pah6HHfmg9WMrK@kxe>+jfoK(WCMz_u~Psi7ceQXtX{yt zF}aUPmxB>(Hv{A3gG^GG&e{BiX$dnu-J=Y150K_^4GuANbqUe&HP$oIGtluhG}JTH HGvERMlF}S; delta 275 zcmZqR|G+mvT#%iCfdPa;pah87H)>pAWMrEBmrmM|Hn18t7WB;LsPxVU+KIboK z_!8e<@YTPq;r9#A4}WfWGW#~;fllS4-e)=H$2P?DR@+8+wj=atYD&# zuJZne1@G5CX!uY#@59IbLk*wmmlk}^U()a;zP;e9e_O-99Woy_uHa#0WSYF6RdVtN zMmAPfHXxk*fHh&V4U;(s*d`z`xt2)=)Aq@$m^^eq*8c|rkP|>O$Qd9Sy2~Ix9sKBy%0RzY6`;5BW dj9}{+7=bi4Cv3K5>S3a>8#rKYn4HZj4FDe?6r%tD delta 254 zcmbQn-NrpZT#%iCfdPa;AP|VzH)?n=1~M@MAu|gTD;t&I!RRy4;Llix?PzG&ajOFJ#hTrkU-^NVapi28Wosx`gQX8tWP98R+;L8tNJ98E^pr DDjgYo delta 264 zcmZ3-JCApQxF9aJdGDn7X=z==d7z8R;45_!=7O8R{8u0RU^z B7Y+ab diff --git a/libs/common/pytz/zoneinfo/America/Indiana/Winamac b/libs/common/pytz/zoneinfo/America/Indiana/Winamac index 630935c1e1a8c1a87e728fc1dcc4c930e81b30e9..6d4e19377e9f70c3782daa223753c640c60ab1eb 100644 GIT binary patch delta 279 zcmeyw+r&3PT#$o-fdPa;ARCA|Hfn5OWMrKz!=~!W#twvxOdyhcIJu2Yfo1gq29C*z zOuF2RV2cu>KSkW04i-6 A(*OVf delta 281 zcmZqT`@}mzT#%iCfdPa;ARCC;H)?EPWM^UoLS~l94;f|M8JSqwfRK?1M3N6D_pvEV z&ScW&1Y5j|6gy#!0`Y7 z&JBz#|NpOEz`!wCoynG$k%19xJDA4iqRrEpWSHscBvlkAak&PEn7X=z==d7z8R;45 N_!=7O8R{8u0RTxGC_n%J delta 305 zcmbQl+r&FTT#%iCfdPa;AQ_0+H)>2^~hM7K2QATnKmuql{sjEwfj<2zvk)DB$uc4uyp`HO3 E0D)T}6aWAK diff --git a/libs/common/pytz/zoneinfo/America/Inuvik b/libs/common/pytz/zoneinfo/America/Inuvik index e107dc44c34fa633deb2f431ebdbb66a31168488..87bb355295a3efcacd4baecb5a8bfa3fb9ce9c54 100644 GIT binary patch delta 35 jcmeyx_l$3XI4c7PY?PVJH2EMCl(+dVQ#H%vBz6e^rJD$< delta 54 rcmaFH_ls|WI4dgy0|V!pJyTkx_NB0VB&~H%7t95sVh-iZ&l$e87mQjDyQY*TC4$ GmiR%BG1Y{1Adc_*{r diff --git a/libs/common/pytz/zoneinfo/America/Kentucky/Louisville b/libs/common/pytz/zoneinfo/America/Kentucky/Louisville index f4c4cf966fa6a0130b9ee5fbd01fe0790fe8f001..3a335b37165d2b3d6a09a1716cb158e272087c9f 100644 GIT binary patch delta 484 zcmca2`b2bsxF81u0|N+yz$PH(n5bdGbGh`o%;hDV1-JffZP=L6!^F+Z$OM5btdkFN zO4YNmBSA(aHnKSF*b1md|Np;!76Zfo|2sD@vi$$QdI1B+X{f>*^nSA4_SsbYyos*Cl_#uOg_MD%Fe(D_Q~Xb%+i=%o2Ap-K-e;@$)4@86f QxOo|i1nbcAgv_WWsxF94D7#K<(cmdV4Ljg@L}vH-Kf7#SF_ p*}ZuR^AZ-?S*{4ToXa&h#MIR#M90@y&q&We$JfwM&rr{R3jlie7(M_1 diff --git a/libs/common/pytz/zoneinfo/America/Knox_IN b/libs/common/pytz/zoneinfo/America/Knox_IN index fcd408d74df43310a9a85c475f83d545f6d75911..025d132dd48ba978c6fedf86d70173127be49d49 100644 GIT binary patch delta 152 zcmew()FV7WT#$o-fdPa;U=|Q_Y}CkMVrOM!Vq{`wnS78*o{??xM?G&X}aFJr#KLX=4yFq0-Ha!LaLn_U^_ delta 138 zcmeAX{v$L&T#%iCfdPa;U=|RwZ`8}$46D|Nfoem}d delta 110 zcmcb`_=9nRxCRRY0|N+y0E{ir0OahDoAzYKyQ^2~|NsBb$izI+N|OVm1_UOE^TL#Z axGWRf6}UkRgi-+z7X-L$bPY`GOt=8yMHM{& diff --git a/libs/common/pytz/zoneinfo/America/Lima b/libs/common/pytz/zoneinfo/America/Lima index d9fec3716117b4ea513bf99f0cf6d989dfa5d256..c13bb6be45a3be6cc747d056c571623528dc1e34 100644 GIT binary patch delta 91 ucmZ3++`&9SoR|z06XRo>Hq)$ diff --git a/libs/common/pytz/zoneinfo/America/Los_Angeles b/libs/common/pytz/zoneinfo/America/Los_Angeles index 9dad4f4c75b373635ccbe634798f8d9e587e36c1..610e7af5fc13d9784de30d272c7c39d7938873a0 100644 GIT binary patch delta 134 zcmbOtwnS`#xF8z?0|N+yz%C$W+oX{f>*^nSA4_SsbYyos*Cl_#uOg_MD%Fe(D_Q~Xb%+i=%o2Ap-K-e;@$)4@86f QxOo|i1nbcA)-yBF0RI2KWCjDn|NpBOFtGgpzjFg42ZZh8 k8^Yim48$%W3=E7wH!x0SWRk>m*=B8~eM|%Hv;3#xT#%W8fdPa;ARLI9H)K>GD8$ literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJfgC7JuG-)X4V~ms?y}Zi%;Vx_w;TA(Apg zg~$pK79uS~T!_37fgut*v_*!WL7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Merida b/libs/common/pytz/zoneinfo/America/Merida index b46298e1f202ee4ec22ba3cf9f8079c83ebd6c7d..17654cb59991e52537f87d9d9a2b56396f9c5c7e 100644 GIT binary patch delta 170 zcmZ3){f2#lGNa{0l{c1*Of1Y~!O8a-^*I?CfB-~*jN5F@bb*mF1Nq?waybWwm~jCB D8M_T@ delta 610 zcmb`@y-Nad9LMqBPocmDp`jsYh$0B=Ji#0qf-r)JgD630iJ&yIhqDK#llkiq9${v}#K9i5q;RB~K&ZV48IoqpyI^}!{0fm7=LE)f~P*^B56dno@bD13P z16B+#iW$Srt82&b^XeKh94VFzPl_qUm10Z(`To(4Hq02`PJZGo5T8uM;=_rM6*VG; M8L}d#(QlZ6UxcZPmH+?% diff --git a/libs/common/pytz/zoneinfo/America/Metlakatla b/libs/common/pytz/zoneinfo/America/Metlakatla index 26356078f8d158e6cea100856baf7620a3296b84..1e94be3d552ea0d6e29824469866c2a51a187be9 100644 GIT binary patch delta 50 zcmZqV?&qGM%os3HWi7Ld+k%M~4lpuKp1`<(F>tdXQ!FD$Y;z}bBqJl!xF`n$0|N+yfC~_V*b*Cc1Q?wenOK-vm{@_34NQVqB*Mw>Sw-zuFJR>P z|9|cT2A2Q-cWz)50FxsB|F2%az~SQ?!r&Vm!r%GABiuHHy{bpaC5Q1Y9>UeJ~_`RAn*%1*_ zy}C>`jM%BxSDE?<+tc4gnSN}tXI{=^rtoRc7VpiugOWXea%wJQZ*6mD*UToK#bPpJ zF7@1qD^vv-6(A)TH6TSG mRUl>HuR7H@u^QFs`)?hWiWX7P{6;ts3IzQ|C>ZDt1YJKqTAaZE diff --git a/libs/common/pytz/zoneinfo/America/Miquelon b/libs/common/pytz/zoneinfo/America/Miquelon index 06ceaadfff38762f411606d9a029c986140050af..5eccd861071d4eccb76fe0d8f5195bd1a7f646b7 100644 GIT binary patch delta 90 zcmbQl`-NwMI4=Vd$k?c|f^qT%#*E39Od9BNn|ql&m|>DKTsFD}#&*U)(#TH7*I3U; P&p^l5&`{4%&wvX6D(Dg4 delta 106 zcmeyuGl_SCI4=tW0|N+yK;}l36^yL)|NsAIoP3cndGZ5BmdW~Tnj8>W1_pHHn;V!s Sm^neR7<$;DdM1an$pQceLKkiT diff --git a/libs/common/pytz/zoneinfo/America/Monterrey b/libs/common/pytz/zoneinfo/America/Monterrey index 7dc50577749baded06400fbe5d2e8dbf2579253e..5eb723c80949d0cf2a99603ea6aba4688ade6b21 100644 GIT binary patch delta 262 zcmeyxb%lL`xF8Dy0|N+yfF%&KOw@Rh%*ez{9{m4*;THym|NmDnU|{+Gf9D294hY-F pH-y1C7>Hd$7#J9V)-XDs!6EX{g!WLaXED*uQyP1_~nuFX>HbrF3l6@>5H!ynLVpLRl_? zNP;eOqtIF;nFSK$B9fqpN+^ONq85Edl5{(r=WJWGi2lRj83tx`ey_L8?~9i|Hp~8n zhn3h5-{)Nzo~(>0o|0vCcTHSvtHieis|0tcBo=;9Ns+0N9REe9x>I`k~j3_L;O}uk^YBWzrkRbVkonwLbrx&a7@!Sus6&Lso&x4)tmG z@&x6c>6VT^e>umA)R*Tc_Jq*;v2c_Ox7WzcMI0ZlY7{6)#)+KGBxu$KhN)CKlVGiETltYg~ zP5b!@-7z?2I=eG;SKkv85cp3%mUZ@vh-F6hah66iO6M6jBsc6j~Hs6k-%+ z6lxT1PHl1wc1~@241N@X42Ben42~3%43-p{6rL2K6s8oa6s{Dq6t+%nx)i+l;tb{#>J07_@)Y(?ZTb}cloOzw0p%1Z=Ri3L%2`lOgK{2}6JeYQ z-$^LUeN_(+(z>Fb9{7u7R_x diff --git a/libs/common/pytz/zoneinfo/America/Montserrat b/libs/common/pytz/zoneinfo/America/Montserrat index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-djCqfV_4#C87n$%`j9Irn$xyv)P*|JoBOhVL2X>t|UO zho{RNzK3i!uOp46vi#Bqade0K{;`Agp5tY)`b2KE=j8S!`cz7xs8~K(SN5Nys-}<9 zRnZJpGva})Y4M8MU$yeIKV6(@+bz%TOAvKe^5warG*y3izHC@AOPt@BB`?fK75>FZ zGLY;WATCb)Asb`02#!hA!On=d?E0oJKkiXiI-cmObzybwc2Hk0X;7h)hxCp7GIew7 zCVgvefx4ZyOy5agr0#k*%6kJRsQYP4<-?s?Mn->?5%E(st?$syo)@Ymw=@GI9Y z6w$FXLB1UqFFI$W>34(2s`tqQb=T0(syntrLWQT}0|~ zzvwNwAV2#Kh%fp3<=15!M14k4-@Y-f(uBCZr6c1~CfT;X%(86zUZ{`Dwu9%aIOn^+ z^By$Yn`2FzYR&?47Me3J$4tet`Z%wDP0lvY+MdEFGC^dD$Rv?zA`?ZXicA)nE;3Yeo(w?K~4`~qT5NQ$V5or?X z5@{3Z)6q1FZ6;GklPa2Aea;2VazVF9yGXxC!$`+S%Sg{i(@57y+eqI?{M9&4`Bk>~(fGh#B2*@%Z3vqsR%p=!d8>bs7S_pvEZZkn@rq6E;#KElWMBj; r0Fv0WZPsB;VaKXZ5w4HRH8{l7)g?s7*I3U;&p^l5&`{4%&wvX6q0|@_ delta 131 zcmaDLeL;GHxF9P70|N+yz#AZD-KepJd9omjArmtTklH@^7>n5CM=Xl$42&QpjM%hn mHepR+$D&6bs)x%pIKSS zusxR+vl`PhwcYBWLDMYMj%DVS9osCIneO*K_0WLxxm*~0_qP zzlnQ|uiR3%JbSNOoXeVBbN*DZ^FoeIUtCe(ymWA#z8sY%u56sI^TsSy`3t7${GJ$9 z81`Hi)+dOoLs#WBk3(ExxBS$iMX9UN8VXDOL*3X%96;j z;%j1_r}cxB9QE+P9{nhB zmwLP*SwC?ksiz6)@>y`4s)=4NUmnu3HvEUI6@#j7XPbWQY*zIv3cU?eFIK;qd&Jvl z$*g`Gy4Cw`&MxOWU!H6V*&&)*4$J0(eWJxxxkbJ|yg{hk1lhWIf%uT&kRKOEiMI9Q z<);~=MEk;M{dvN4)e#x2J12cpUA8{m_3O3jZm-il4JGQ!Q;+VgsuN#}YNbBw5q;Tr z&~=Sb^F?@04V_ohbsNdL$NkR9;<{fvPZ3<4PiG7MxK z$Uu;hAVWdMf(!;34Kf^LJf_BgkP#s}@~<==7!xndLGi$-_!z?i#)S+F85uG(WNgUb QkkQ>v3A9@Ox4LD20f+35WB>pF delta 136 zcmZ1`eM(@0vT8d60|P4%i!=a>sL2!ABsOPqu&|&RVKKRn GR{{X=lRq8+ diff --git a/libs/common/pytz/zoneinfo/America/Noronha b/libs/common/pytz/zoneinfo/America/Noronha index 95ff8a2573f4dbb03892a576c198573895a01985..73b4b336ab55544e6061409261c16cacc39aff61 100644 GIT binary patch delta 75 zcmcb?x{q~&I4=VdP@kx>Wa0&7W=1B)$*Y;<(PcN=Fgh?I$#Zbo=o%Q=8F2vs`AQ88 delta 94 zcmdnTdV_U>I4?5;0|N+yfW}0XB`o#-|Noy{$fQ2;oje;v7+uw7Lq-Qi4v-v@T0V$c ME*o6~BReB50L!Km;Q#;t diff --git a/libs/common/pytz/zoneinfo/America/North_Dakota/Beulah b/libs/common/pytz/zoneinfo/America/North_Dakota/Beulah index 246345dde7cada7b0de81cc23fabc66b60d51e79..33e317e25b127335de68052f57d33ceed620524e 100644 GIT binary patch delta 228 zcmX>jbVq1{xF81u0|N+yz%(G{*r*Z1#K=0?nm3(%7uqJdJq^3l;6-fY~?MkW(4}upJlH delta 234 zcmca3bVg``xF94D8#LmPBgv>0HCo%<1p3lT)&c;d>oGitxFxi1w en-gq210w?iHXAq3W8T6-Z7bOzR!(;0lm-BFLKNHp diff --git a/libs/common/pytz/zoneinfo/America/North_Dakota/Center b/libs/common/pytz/zoneinfo/America/North_Dakota/Center index 1fa0703778034551487b7b55d80d41ad220f2102..17fe13bcc1c73ecbc6738499667816f54c164e39 100644 GIT binary patch delta 300 zcmX>jbVq1{xF81u0|N+yz%(G{*r*Z1#K=0?o5L!aosDL2@&gV9mbnucI41w&(B)4D8#LmPBgv>0HCo<_xp3lS<&BjVII9ZBWVX_0W eHYeB#42%p6*j%-F9`hEKfp8WZ#95OaIi&&Mm=jbVq1{xF81u0|N+yz%(G{*r*Z1#K=0?n4D8#LmPBgv>0HCo;KAp3lVQ%En3sI9ZBWVX_0W eHYeCJ21W)3Z1!)S$GnAwwhmx}IAF3Pr!)W`XBBn; diff --git a/libs/common/pytz/zoneinfo/America/Nuuk b/libs/common/pytz/zoneinfo/America/Nuuk new file mode 100644 index 0000000000000000000000000000000000000000..0d2149cc0033785b517624400583fb9edf2ff2ec GIT binary patch literal 1864 zcmdVaYfQ~?9LMpKG}g?%q(V0&xt!{Bjv^|TR6~c3@BKHAJo3cm-~RvCS&zT(k7wrMBA4xt$ItwSmuHQ6 z`Q2srhGJWQyDGbf>$?U7+xN zzN-DuYmKRYps|~8Xx!>E>L@&>@%i_~S=J=3lx7*4vqKWX_DZ6&LK53+WOzuu4u3pP zl75uyh_h)j@?oAPHxy_}^9W7dGDb)34%N{`kve8`l8nvp)3m}685i|Z)6?1|!|%Fg zMm~|u_b+6;?*$p(a!n?*9h8ZUMxPV z{YK}HIxP$GZ)#!0E?Jn;s70Mm>Y^~!MXz>haeJ9AzE-JA9(#1@;aOU8b)_uZnxdr* zxw5=CLRV}_kd;%CrK~7eR>ghy22rkhR5;{vt(!!xPq`b8`5T$alB_jT=w23gnA ztW~=%YIWmYU0-=fYwBv$r(*}-fBxt7-riuddA+T7Y#o1p+n)M#_ImkdxjnWRhs(T9 zb9FvGzdoM7I8?8BI947G7CBnvaFOFh4%pHhF>=VrF(U_!95r&-$Z;bFjvP61=*Y1n z2ag;*a`?#cBLN^0Seg)!7?2>4D3CCaIFLY)NRUvFSdd_lXpnG_c#wdQh%8M=NKBR{ zC?qN*EF>-@FeEZ0G$b}8I3zkGJS09OKqNvWL?lK_6C@I)r3n*>6A2WF6bTiH6$uuJ z76})L7YP`N7zr7P+0q1!L~UupM&d>SMeQG9<{DAcKO8ilrGAWLzxGz#t=o3=J|i q$lxHOgA5P;SL5TKYz8USWxmc-yW16;5bKDGaoXMSj$c3H{eJ>Mv$!h& literal 0 HcmV?d00001 diff --git a/libs/common/pytz/zoneinfo/America/Ojinaga b/libs/common/pytz/zoneinfo/America/Ojinaga index 37d78301bd100b7c34b183a7e355021f55cb366e..f81827e840b0156317cd9a6e85d49160aa5127bd 100644 GIT binary patch delta 352 zcmaFDeU4*-xF81u0|N+yfDI6HOw{;TAE(d2$i%|T!o&)M%s>W7km>*bc`^(P|NqaO zz`*kV|LO${93YaB2PU#}1ET;;1SIR@8^Yil9KzrX#J(;e41CT&f`Jk2CLldohe;OG YotvYW9x@KJOQld<3UM(P&>v=80D{R$i~s-t literal 1508 zcmd7RO-NKx9ES0uS&Eomq99NrC=fy?XUxycGOfvUvUHjzGo4BuCQGN3!OD*VNz%$w zhyjI?hgq^V^lEJ=@nZ_KL*nh_ZaDp z7cwJqlaU$!U1YwGF|rn(i|lW!)Yj3bB4;RCZF|rsa=SjM?Y-?{N5d19*U>9?mJF%< z>Q-5>`KsDgP$hTAH7V!DJn5XjViZPfkcHEY#@^ZGvgrDNDt=ZWT`iqz-)OEZsR*e3 zJ?rIxoFY{k3YTRG$;y3jR=R)0DNouX>3N%Ec-OrV-icV_@X~}RzcptZnYk@0I;M=H zv3lY<2S8 z7g^VH$T&3_D^7>9jQYVj(cpcX^FwzOSTtz+4;t+Gb7UrUf-;XKRq6A|`rAJgnl5W+P3l!!3R zJ2ym>Y2F_Si{U%W`1O0S%Pm}GZjsMhpuhF|`?>$37*ikBAmt$SAO#^6S?ZGbA8O(# z3aN^tETk@^Fr+f1G)r9@QXEHhNO>IfAq8?&h?K}tBT^(%B~m6*CsHUXGs}>PHs9u>xcX9BV)p z!LbTt8OS=2g&-?ImSU;bf-J^TuLfC;rCtxRAY?_zk~r3cEQ(`Q$g()rg)EF?WysPj z_1cidS?bjx%d^z$Ll%gv5LqI!Mr4u5D)IkcX0)DdhMsGZE7j((r6jrRcAMRn68#fq Cu8G0` diff --git a/libs/common/pytz/zoneinfo/America/Panama b/libs/common/pytz/zoneinfo/America/Panama index 55b083463a8b5b19d62b6f2707665c08eca5e65b..9964b9a33452f4b636f43703b7cdec4891cbda5f 100644 GIT binary patch delta 33 ecmX@axQ%gwI4c7POq8*JGs_&Jp;kYpiFaXQ1P2XsBnX HXTSvju%!%e diff --git a/libs/common/pytz/zoneinfo/America/Port_of_Spain b/libs/common/pytz/zoneinfo/America/Port_of_Spain index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dfdPa;Kw+Xv3rqd~|Nkd$5S_T6ZL$Fq%fw^aoDfMMIq`!8s`|yE XjEo#0X$(z5a7_$cHo69;cBWha!o(PR diff --git a/libs/common/pytz/zoneinfo/America/Porto_Velho b/libs/common/pytz/zoneinfo/America/Porto_Velho index 10cb02b8b9bba9ef4171080ba53fc1220e7a47c5..e00398602c634e6f1e9c0bc0e12b064f343ffe0c 100644 GIT binary patch delta 116 ucmX@ZvWaDaI4=Vdke#SfBg4dq8%{hZkFI4h7vl#)dN{ajbPY`GOt=7lT?(-P delta 98 zcmdnQa)xDsI4?5;0|N+yfZRlt8kYM1|Nl?^$SBRk$TV?}JR3v?L*f4q93UA~#e5LO MTsFD}CUz!V05PZ)bpQYW diff --git a/libs/common/pytz/zoneinfo/America/Punta_Arenas b/libs/common/pytz/zoneinfo/America/Punta_Arenas index a5a8af52c2f26baf6f85a1786f69593491ad5195..411a839b84d3c629bf7eb623024077efe614ed07 100644 GIT binary patch delta 171 zcmaFI_l|FZv7qbhn5O~rYaSh4$WVA-LtOdMi7o~#SHvGXY%J+u6lP>%WM*Vxfa|KHofzyKt7Pp)TbRdoFi1R#SzG{`Uz4Kff!gAARl%A5i=b#p7T05fsM PdU0^s=o%Q?8FK*u6FNj} delta 173 zcmaFI_l|FZv7p=Rn5O~rYaSh2$WVA-LtOc>i7o~kD>@j(>i_@$&&b5c%*et7gUn3K zlQ*y>akKybzqf^f0Z8teT+h_1=mu2!A7l=Q2AKt-LFR#IkeQQJnNv1TWaeR(VgQ1A TpeRu$dvS2t=o%Q?8FK*uy46Qy diff --git a/libs/common/pytz/zoneinfo/America/Rainy_River b/libs/common/pytz/zoneinfo/America/Rainy_River index ea66099155ca930810062eebcfbc7fc99e97097f..ac40299f6b27043e8f2454ac594b0ec184c1a237 100644 GIT binary patch literal 2868 zcmeH|ZA_JA9EbnMHxNb5#0UvYD~2Khis1`osbi1`>Jd@M)G)*{9^X)W!GDX1ep0i1 z8Aa--yvPWN?JTU&l{>tnyO`?;R;d^qQPIoIW# zp7&au{A+&Z35Ojthx=Y?E~TFP4W&m9rk7Rj&<_vZb&hNwsYla_-EY^n>FNG=oYS*9 zyESX)%9+VC-Lq59N^Reh?)UNg<(yQwbwS&tu3@8l-hABo6WQ*C`Yrn7t_@CoRiXa0 zZlTjqmZcZ-yiQ|LqFzcK=3Jf;u2&+1ou;Hw@^jCj?$wAexq7R&d(HfsYt=38^`=I- zQG3j7K7LGo+4!Y@~V>eavRDec_jum;E;4RCjAmxfK+weBkkJiJ7^ z9oQk=cfBJ$w!JNntXnKS3+KqAc^T4cT9O1MCrIz%k@8q%hy?c=r;q!$N=Um14Y?Jp zeHuG!X!SjP;_K@gw&#L|?>MP_3%bjbZ&hf&DZfhpdEd!X@ip>v%IES-&=(RhV1q=o zRcmCxLW!*3s{^iiWnk5N8dc-ypt7YJy?2rhE}Et>n}%v^Zh^!t>a9bPGG*wb7LAV> zA;aQ+&}aQZC85u6lF-y9!|&J0h~o`1^86tgRne-6hdz+!*4OLvrK@Fh{$YKgxKv+E z+o@x6R_WNm#X4^6L}%RX{gO0jzME9DO_BrC+~mD0B&9jled+xzI=*&-le)4{C+v%H z(z3HOeREG|;;2NOwB(L6IW%0oQ)(S=t4A~9E1irhVUihk#?AajWLn!kcY1lFWYuqV zvx|?(%T>$W895)zD`na4%+w<~tEAAGJ*Zq?&CPOh0@vuAq(o;<^IXl12zPQ%rf8m@ z$I1IFO6N6Qa$et@EN`6hx$_GK%9|Az-TaI|an>Jo7sTI`h4~-3i-M|UaaxJHxUE?W z63U%|`ct|rc#X5HYL6DS%ypKRZPFFzQk<1VOLSFblv6Zgs;=JJ&1vVcul61Oy7}|% zgRUJr{kN|NeaFK*^ZCkei1>U5c6&Pbe4lO?e|z86UVHrW`S?_?j2UarWOJsPlkPSD zZV`{iVVG^T;r24WnDbs*+}*au=Dh=m{~g4hURB#4zDW`fws(ijS2DNAE2h^-*T zf>;Y;E{MG#27_1(Vlqo(GljbQ`#}r{u^_~R z5F1(=BSNeQF(bo{5JNI7$uK3umJDMutjRDZ!=4O-GAzn4Da57>qgooPLd*)WE5xu6 z%R)>Gu`R>65bH9`%djuQzzhpBOl)av3^B5$u`3Q){*VGdDgY^grKtg<2$rS_ zj51i7Ixq?WsRX1HkXk^B0jUP09FTfI3IeGJq$HN6CXk|7nyN6$VrlBaC=8@BjM6}A z!zd1H{edq(YDqL23jklBKBHrfQI~S(>^*3TJ652Pqw-c97yhss||_q<--K0{&gV029YyCX!L9L!#rN PW1>=HW20lEV*>sFRXeOf literal 2122 zcmds%T};(w9LJwSHjzZhq!0;wDG5CwU?`gOCX7KI@#S~`A@c=8EaR8LNz~})#-uLN zP0DmNj7GIqO^Ib#i|lo+mocQdTE(=MYi`t2d11*oJ)h^ctFGGm{`Wke|Nb}U=6pWk zbq)9Dn}6(T_l3jS-Qj(zx~sFVOOL(%r+j+3a>M73kLj7&m*mXG2{wMlfKEtyLlR7n zCfKLsnsXgG@tYGi@pQ9JI(5iSe&vu!I@)BfeWJ-sX^GhD8X{(Db)iizD>T}~Jb zn+k@^&E?1Joa8r6=G=WYb8Jwv5;ohc;a;74vBb{ne_De>nmievKn%gl) z^6H}|e`mZbD6KNLuD&P*nM=&V{4ZqD_%u^E{byTvVazPP@|j)o$vJb|x3Afy$49m3 zodfpvgTs2qvtsXTJgs-NJt=oryr9eK8l`xCo0b%>l9E5(GNswuZRyZaQw6{f9oXnMq+8SMXc#5oATBNI+FUgv;3=OY6E8)?AMhf~Q@>9C0 zO8?4MeQZqi*h#yt`v+4qe8kqaePr(K-)+~|y=3m|thF0LALz#R7TJ{Dt()s>q%Lv4 zZYeF2EhAgCJ~Kn=zbw~=@d0UgJ*e9*{3s6`E;A1f#O(IyJoC`;^R_XPXyo9B_ThqG z&5p(w?at&u(^S!Jo5n^oTG%bo;Q`&1vR`)f_h`%It+KnbL-%}LE_+*>_0hhdw63qz z$Bs;rxIkRss&U-^=@W~+TxCowcIrf6TrBqL^CsTEPxN=v@=(|;D|Tm%JC*LN47lH#n20)7(+9NY7E^V$}zO_bm~F$gD42m5TYVPM~IRPEg@<$^n@tN&=jI7Lsw6y zEJRyRr!GWah{6z!Au2<3hA0iu8lpBsZ;0Xy%^|8YbcZO<(B9Lj&(I%|07eENDS+ev zk_1Q=AZdW)0g?zvCLpPRgOLqSmkvfgJY7N<8G)n(k`qW$AX$N=1(Fv?Vj!7; zqy~~3PnR4>c065r82RyZ31Vajk|IWqAW33m36ds8o*;>WWD1fhNUk8sf@I6nr3;cT zPnR%A#ynlhAUT61jgd7-+8B9*B#x0eNa`55gCviUJx`ZDNd7!s0wEdnbSZ@75Rybl m79nYb>Hf~)63SJ7Z-K~8>7ZZMRWmy?&1oA5XN!8Dlw diff --git a/libs/common/pytz/zoneinfo/America/Rankin_Inlet b/libs/common/pytz/zoneinfo/America/Rankin_Inlet index 61ff6fcb7dc92b4d41dd0854a2c9e0be939be6b9..3a70587472c633aaa178c7f979b8b3884d604372 100644 GIT binary patch delta 54 zcmeyv_k?ePI4c7PY?PV91Y>W0z*NB^!Q~trV&?1;qT_3Wa0&7W=1B)$*Y;<(PcN=Fgh?I$#Zbo=o%Q?8FK*u`A`iG delta 94 zcmdnTdV_U>I4?5;0|N+yfW}0XB`o#-|Noy{$fQ2;oje;v7+uw7Lq-Qi4v-v@T0V$c ME*o6~V>@Fm0L!cs;{X5v diff --git a/libs/common/pytz/zoneinfo/America/Resolute b/libs/common/pytz/zoneinfo/America/Resolute index 4365a5c816c5c487f7ecc155e6c06c57e5b64139..0a73b753ba597f89cd57cc3875e7869dc133778c 100644 GIT binary patch delta 54 zcmeyv_k?ePI4c7PY?PV91Y>W0z*NB^!Q~trV&?1;qT_3fdPa;Kw+Xv3rqd~|Nkd$5S_T6ZL$Fq%fw^aoDfMMIq`!8s`|yE XjEo#0X$(z5a7_$cHo69;cBWha!o(PR diff --git a/libs/common/pytz/zoneinfo/America/Rosario b/libs/common/pytz/zoneinfo/America/Rosario index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..2ad6ea5db204d599266412558f5a1fae076fc41a 100644 GIT binary patch delta 81 zcmX@Zv5aGaI4=Vdu$`##hmmozA*1Z%2h8%5lNc?~6>L7m_<<2s2?v*ru7RIm0|N+yfZaruKP>hC|Noz?z-T);hS8D-A`T(Z6>r|j_<@lFB!{d; P1X&B0jjn;QoiP^xp@|eX diff --git a/libs/common/pytz/zoneinfo/America/Santa_Isabel b/libs/common/pytz/zoneinfo/America/Santa_Isabel index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..63dfdf48a68d02240737ecd6af081e02eb0b6317 100644 GIT binary patch delta 551 zcmZ1`bWCW1xFiPy0|N+yz*Haxu_cyndr`2_Ac(1+nTdsol?_M?49vl1?f?Iid>9!1 z|DQX7f#v`I{ssmP7<=vnMjjBGQ2mv`lD%xFj0`0|N+yz*Haxu_cyn`_{10Ac(1+k%^g!g%wB)42;2M?f?Iid>9!1 z|DQX7f#v`I{ssmP5Sx()ObUQW5g*?W2H)Tih5!(D0pf5V4hR8xl7WE{LQW8u#p?P0 VKmZDW%?p_qu?*`Fn5@Ss0RTUKN9OF;(^Q)52|uNG%zrrn=qM+@rN2nk|<*Y PV8(FS=o%Q?8FK*u`o$78 diff --git a/libs/common/pytz/zoneinfo/America/Santiago b/libs/common/pytz/zoneinfo/America/Santiago index 816a0428188d99f437004312ee73c3860ee0f54f..010c6bd04cae79078540da560ce38400bfe0ade6 100644 GIT binary patch delta 218 zcmaDTd|7ycvf_LO1_llw7HI$y{RZx?vtynH%&&QLbRk3GfemrxM<=?NGK*g|*jTfX zky$p@VX^^Jg$N@PBQqll6AUslF|)EvKETw?_5c6g-3$yMa&j1Rt|i>?|3Hx0E_)P2 sgDe5jAd5gW$TAQOvJgarES>y^ITvK><{Xw(X0Wwa*gp_uZ5d}a0H5MkAOHXW delta 248 zcmcaC{7`s;vf=^;1_llw7HI$y{RZxCvtynH%&&QLY#~G8femrx$0oX%GE1M_u(4(% zBeRIofyo9;6;k#8|Nm!XVq|7yVS+(sCT3P3p8SxbcJc=f9&Wb(|M%`EYh2ckiCf@qMvAR1&hhz8j|`8#ti$N`(vSyGw7E;!HrK@Mmd QLp@LhNv`mkyopm00GOC#8UO$Q diff --git a/libs/common/pytz/zoneinfo/America/Santo_Domingo b/libs/common/pytz/zoneinfo/America/Santo_Domingo index 4e5eba52b8a86c379efae694a296d51e1008141c..4fe36fd4c11f998ba3f626c5e23d97bd82d12791 100644 GIT binary patch delta 33 fcmaFFe2RI3I4c7POq5vyXTEsB#^o3sV!{OgiG2t2 delta 58 pcmX@b{D^siI4c_i0|VPcnI$|x8V=y%cU}m=1%V7M$KVhXE&vbI2p0eV diff --git a/libs/common/pytz/zoneinfo/America/Sao_Paulo b/libs/common/pytz/zoneinfo/America/Sao_Paulo index c417ba1da757e94b88919b05df8a21b35a5bf66d..67935ff4da8f527e03cd05ed2aa20e601e10f921 100644 GIT binary patch delta 70 wcmcb_KaG2WI4=Vdh~B7D!Z_K0DSC1ryBxaQ<}XYSm|&6|TsFD}#&*VB0L|EQ13aJSs=yWeo%wAbEPt0)R?T)B!hRin>T zM~vBtV>WkWHRiP{U)UZde+%b#{mr?IzqIB=n2sj=6d4{Zsk6Vzs4>!w&A6sn1}kv Y<#CCp(_{B)nnQC?KiR!bhYJVuFV&%*3;+NC diff --git a/libs/common/pytz/zoneinfo/America/Scoresbysund b/libs/common/pytz/zoneinfo/America/Scoresbysund index e20e9e1c4272adc40c07562bb3d6767cb056b550..286d13216eb3b14bf9fec1fceebf2fe731472d39 100644 GIT binary patch delta 39 qcmeyv_l|FZGGpmRl{<`+=dqPeu42k&EZzK!X#+Eq>oGZoT>$_+kPhVl delta 53 ycmaFI_lIwSGGp0Bl{<_q_5c6>pFEMRbTTWu=j6RiK;g{~nKm$UFaQCPTqXcnkQGV* diff --git a/libs/common/pytz/zoneinfo/America/Shiprock b/libs/common/pytz/zoneinfo/America/Shiprock index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..abb2b974a47eb3e5c8b4f5d4370baf4898b239ab 100644 GIT binary patch delta 145 zcmeAXo+CU#T#$`{fdPa;U>*>&ZPch?;$~)Kf*>&Zq%q@;$~umLS~l98<{3fw&N6=9K)>0&cFy#$B0ee n=5@?pSa4~Shic^V4GuB)bqUe&HP$oIGtluhG}JTHGvERMYMd1i diff --git a/libs/common/pytz/zoneinfo/America/St_Barthelemy b/libs/common/pytz/zoneinfo/America/St_Barthelemy index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..a662a57137b69e8ba445e899566222cdd422a764 100644 GIT binary patch literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-dX`rV^4u^l-@waFg%5>%F-Ky$v zfxf-JOx(#oA@%A4QJuL<-d&I_?xkeO`xEDh2eE1LVV|+$QAmP(+;Oh@%O_Gk@f@R` zJRYrUuJ=|?&qnBHM_VfA9)IoH=u)<9r*>O%S=E}WbZzMr?&6uOGfWAOs7tbL=mFu` zx7UxyZiaWYj%{~=z z__*%p@lR)ZkT1<&e`+!k(Tihwg4GV#nF#uq<~mJTgR%m{TD}`uobb z_@g4O=AIlCo+n0K^U!-;n(IH|=Rf2Q`_zK6bkuu5So=Do-N=~adC6cou^z>uZ>YY@7 zJtMzNrNle6%q&pvhATZYC0ot%JD_LB&Qr6Umt< zeE)2uNY8M{`FmQ4j0rJv!Iw5s%k6poYP&zrum4lOb-4;w*laG>kzzM@m#c7_&C~ks zZGAQzVvn;8=x^SU=6%b&!{W?%*=%msN8EFap36KlZ>LovI;YY?F2>=oSBm_tdkRTvYK*E5;!O{c* zi3Ab~Bo;_8kZ2&`K;nS}1c?X|5+o)_P>`q~VL{@81jf=t1_=!k8zeYrMTakhhsVSR z2oMq>Bt%GzkRTyZLc)Z^2?-PuDN7S7BvweUkZ2*{LgIx442c*LG9+e5(2%GhVMF4E z1P+ND5;{v0J0y5W^pNl&@k0WLL=Xuf5}O(dL1 zJduDR5k*3Z#1siC5>+IuNL-P?B9TQxYiVMO1Q&@e5?&;}NPv+DBOyj&j072pG7@Ga z&PbpwO{9@fTbfuS!L~HfM#7E68wofPaU|qO%#olYQAfg##2pE|rHMQedP@^~B>0vl z`bhYZ_#+1Zas(iU0CEf<2LW;vAcp~R93Te*awH&!f~7eYkb}X}91Y0fU}=sAM-##_e!|ATe{f01&0NPcCmNu8r(HF)a!2+Ad$WR delta 538 zcmZ1`Jxg$cxCR>o0|N+yKsOMB*b)suqF<%@!Rd|<1(%e+6kJO9J$XH&0n8NdYh^;^P~_;2a#n;0nUw zKpX(XE9!1 z|DQX7f#v`I{ssmP7<=vnMjjBGQ2mv`lD%xFj0`0|N+yz*Haxu_cyn`_{10Ac(1+k%^g!g%wB)42;2M?f?Iid>9!1 z|DQX7f#v`I{ssmP5Sx()ObUQW5g*?W2H)Tih5!(D0pf5V4hR8xl7WE{LQW8u#p?P0 VKmZDW%?p_qu?*`Fn5@Ss0RTUKN9Ojc1CQ1CzB(`MsFJyAho%Zjc1CQ1CsT0kMsFJyAho%ZFGDP~{(ngvQ>GqA6a) zH6&V^LfVbSrf6yj_m;Bvxv8NJ?tU-a4flVZx~jV9^K>{I4sI?>-JHF*zKmuS^p)_d zTs^%IYvXmfp4b-~-4z)NE{a&QBoo6kBGHJ;Wc#2<-h|}ldrNFx#*FRf0evSQG*Y#1 zJ-sk$?4G^pdsA*BlYZ2*o~DtTtLnK{r_4{+MgH|e9`skl!9zn9K1<^8x+afa;^O$^ zTo&E;A#t*tmDbgmC`)arT-S!mUz)$%)8^IrTaAah@V_Bwec?dB448h`4?Y!~wEzGB diff --git a/libs/common/pytz/zoneinfo/America/Winnipeg b/libs/common/pytz/zoneinfo/America/Winnipeg index 3718d47da0baeadef58f3f2719a8983a1a27bece..ac40299f6b27043e8f2454ac594b0ec184c1a237 100644 GIT binary patch delta 57 zcmX>kwnc1$GUKj|Djyg(OE6V2PF~1tG}(ZeZ}L4ZpULtpK&j2nEO(eUt8dH%_rY4Ezmrr_Ow>Vx0PIST&HXD38(SvN#2;TMSB zDdG^*E87q|&D9_-`6xqt#R3~fCPpS^AO=B@K_F1jz`(-5Flz$?2QpjNz<@#9z=A>B z&=91E5kiEJ;I{uj0CFElC&-N;8stt84RR}p2DukRgWL?FLGA|8Ah&~Pko!S2$PXYI UVx0PIST&HXD38(SvN#2;TNd? z|NlQD6C)Ed6C)!?69^PEfb`AU05So@)-^C-&^EAO&^9y#NrTiehLB)C(2D;cH-I#Q m+ySCNZUNCC_kd`Sn?N+kT_76dHV{p&`?zd?F1FLP-~s@fiZV$6 diff --git a/libs/common/pytz/zoneinfo/Antarctica/Davis b/libs/common/pytz/zoneinfo/Antarctica/Davis index 916f2c25926bf444b7c366110a29a3fafb17fbc0..d4d47b24647bcabc981440f0859e94228428798d 100644 GIT binary patch delta 36 kcmZ3 diff --git a/libs/common/pytz/zoneinfo/Antarctica/Macquarie b/libs/common/pytz/zoneinfo/Antarctica/Macquarie index 83c308addc4ca5a3f7eac89d19cd0881f7ddce9a..9e7cc687d76b00d8f112245d5c5d2f20a2a61814 100644 GIT binary patch literal 2260 zcmds%TTGU99LK*e@Qxr9Er)HLbH-)ANRONBcqDnRrq&DjPK8=Cg{)Dbmc)b&8!^Z?WHHDc)0P@x7aE)@ZWL z4i;F#aHJ(RCfQxb1~jKCU~}z)&0Bq3cQ^m2d*L&;QO|8DAktrjB5FbD9d~9oK^&Wv6WkTw5sM~TU`*;19=0MpYggC zBp$Uw?}J(sep+jO?^e-MCow58nE*B08trAw@AWwQBw zaaKMj(ki0Itzz;gd*te6tGsf-sy-UB>Y;vn^z0>Vcwv27imZ;zjzX-}NI&9)uBZco|| zw!P(RYuOsE)~Y|Wqj*d^bHCEAln=E#ZcuFzr_^@+HSHNYr1mdOPkqp;y>Hj)>46U0 z*B!9^N48twK&?HqW4(DoLc=`X(ErTi{LU^fb}3AHrQZ`F-8$d8|NAE0=hLc!GQac6 z_w&)?;QtrXnRM9ajS*%-m>FTFgqag&QkYp`riI;kabRNL%nUI#U~a(VFtY=u=h~Sc zW`eGr8DggB+Lo0+0qw9)Lt(G6AFllM5gj zm~3Fu0ptUd5FjI%l(=?sf=LRH6--(%dBG$GlNlg2nA`x#!DI(W4<56B*nJ|KTU0)Y$yDFkweNup~fiX&nxFS0P0|N+yKnxIr*g_3JV)oUFhBLGfrWu#)&@pSn8+-BFb5{Vz^7|qz~JZ_90DXMW$ G$OQm87(xXA diff --git a/libs/common/pytz/zoneinfo/Antarctica/Mawson b/libs/common/pytz/zoneinfo/Antarctica/Mawson index e1f0b09b9ed9e6fb30389a35c89c0a1149bde73d..6d93f6e1d3f76bcb6325f503958d19798b098fa0 100644 GIT binary patch literal 185 zcmWHE%1kq2zzdjwvdlot^GLy{!RwZi10zTT3_=nZSQr@G0vI@S4Gb8x4a|Vplp%x! XGyekt$Xt+d#F))x1GK?T*OUtYy$BV5 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Oyjf>DFlEhUHg|Ns9pGBH7985lwm7+4q>+yWRlbPWs` lv<=LF*c2)mLW1!?Q~!gk0BHqT1ENW=ipvIQt(~qZ7Xb4-9Ekt` diff --git a/libs/common/pytz/zoneinfo/Antarctica/McMurdo b/libs/common/pytz/zoneinfo/Antarctica/McMurdo index 60bcef686badda46f36652694d06d041c70a9d87..6575fdce31183d8238b18f2f30ab5b9227c7071c 100644 GIT binary patch delta 51 zcmbO%+$ualoRMLpL<%#A-aMQ63ac!aUsQ02uAz}%luL+?uce-;o`H_9iJqaJfxa;p E09{QCcmMzZ delta 40 lcmZn_o-8~;oRNK_L<%!I0|bEho2M{eVP%8xC#Q4D0RW#S2Oa7&C;BUZmR R$V3ppZz`7!(0n^%E&!2a70dtt diff --git a/libs/common/pytz/zoneinfo/Antarctica/South_Pole b/libs/common/pytz/zoneinfo/Antarctica/South_Pole index 60bcef686badda46f36652694d06d041c70a9d87..6575fdce31183d8238b18f2f30ab5b9227c7071c 100644 GIT binary patch delta 51 zcmbO%+$ualoRMLpL<%#A-aMQ63ac!aUsQ02uAz}%luL+?uce-;o`H_9iJqaJfxa;p E09{QCcmMzZ delta 40 lcmZn_o-8~;oRNK_L<%!I0|bEho2M{eVP%8xC#Q4D0RW#S2Oaw@J0gTR@FEA=Fp$O_u&R|gj06HTJV*mgE delta 61 zcmeyv(ZxAInbBpViU=cX{r~^}nI}6kT1{f%n5@g9J9#-HP-^pOMg=BL1|UF__XhxO C;1ehS diff --git a/libs/common/pytz/zoneinfo/Antarctica/Vostok b/libs/common/pytz/zoneinfo/Antarctica/Vostok index 5696abf51d60ca0489d57f1545f47762378bf2e8..62bdcac14db3f464ff561e32db2c6b55c0cb1866 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCvEkpY6d)%^2BbVBfq})xH-tgkz>Fb;1VjD<0ZxOsY=Fku I>6&o?0LBIr)c^nh literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol|L}x?&|Ns9P86gr33?T^&EV>2;4B7@}V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&o?06nM}IRF3v diff --git a/libs/common/pytz/zoneinfo/Arctic/Longyearbyen b/libs/common/pytz/zoneinfo/Arctic/Longyearbyen index c6842af88c290ac7676c84846505884bbdcf652f..7f6d958f8630cba512d8e58ca8edfbd516291522 100644 GIT binary patch delta 761 zcmX>k_)BntxG5(C0|N+yKtB+J*g_3JVm6O>!tATJKFqnfZo=FpTPMuhIbp*5mgy5t za28IOXd_{AaoaS8OA-4TE-myIxST9K;flBNgsYYp6|SZ~pKx_WF~c>@Edtkdqy%no zP7}Dvba2AWmlXmVr(9;NXJ&yzRz?t;4NS7IbI=8vqgcbhz|#TrEQ5>!BMSoqp8&}J z$ZRbGMqUO65II?ZSyI5+)i(sBoRJZT7#SEQ8!*d-VhzIoK#;#E@dt}K|53l%|&!G$e D`9jF! delta 681 zcmew*ct~)9xGFmX0|N+yKpzl;*t`uuVz&BYhS^uD6HaXRO_=B-;czL5UEuY4g$Zv~ z_z1k6VJq;iLr>s+g}lIr3;}_U5sU(#+&>9?Hh(PeMg5AvSMj3)-vmBP_^!_;@cqN3 z2}0+lf7m$VGGjd>GYb~T#L7k=sD*3|0|S=`(4P!43XCia4159%oG=n3GFg#X5)>AU zKrq>hSY`TqIW7eAQ}{4U>X=`AQ}{KllL;4)Pq6}BmfFM5Df}J5Df}N5Df}R z5Df}V5Df}Z5Df}d5Df}h5Df}l5Df}p5Df}t5Df}x5Df}#FbxcGkjFrw4x~0muq$p(yJ=rWtPFfL$(%5iepXd9T>>6&o?0QHv+ Ak^lez delta 105 zcmcc4{*!%zI4>6i0|N+yfXPIaD=hW@|No!(!+5e5V>nD4C^fl}5l!vprHl(0IY8Qw TbO?j^5Wr=lZD3}nYsLit=OGp( diff --git a/libs/common/pytz/zoneinfo/Asia/Amman b/libs/common/pytz/zoneinfo/Asia/Amman index 281b304e2f8bd4e9bfa58ccc1c799d3ab12f1da8..0a8e350a334bbee29b155fb9944c6831460d973e 100644 GIT binary patch delta 218 zcmX@kHC zQDESNkq{9r0|s6n-w*~@*I*D4!k}$n3{ua?$cW31&DWULGI0Yf0jYypSP_UI685s?uzOH0%c z5zP@1)F0sJFCu7=wrH&1+ui7nyVq~H;Xd!P=BKtJcZX#d1`k(GKb%{y?J2Vu?Um&8 zkIeKn$?Wi}q^u7~cRon^{aG^3s>(cG%G}$%nm<33g@>|QJiJiZ^1fQi9;oHLv|1U< zs@3GETJuh*^;k-BZ2^_HMWH1nszgP#;4+zv~x3oSZWN diff --git a/libs/common/pytz/zoneinfo/Asia/Anadyr b/libs/common/pytz/zoneinfo/Asia/Anadyr index 6a966013d9cd9007a75318d3c8c757dfe8a72373..35c531c0709fabace97e52cacef74b4cfbbcd820 100644 GIT binary patch delta 72 zcmdnNIgN9II4=VbaDfOQaM-BA!8qA~(P45SV-&jF=AVolOfX36i0|N+yfa69L4n~&x|Ns9_)?sv*Y{(J~lL1PitKIyHk%Nf?qzzev S2uJ_|xNNiyjqG#{jko|Ve-jM= diff --git a/libs/common/pytz/zoneinfo/Asia/Aqtau b/libs/common/pytz/zoneinfo/Asia/Aqtau index 78cbcf0ef6617bf5efd26f866fd195dfe08e3891..0e1c16d32eca7652aa7e1392e606cf5b54e26bc5 100644 GIT binary patch delta 62 zcmaFOev*BH_~dzv9IOmLU^G$X!eoDDqsdLoVaURp*E7ywgh_C6*=QS>+Uc5d0RSV> B5C;GN delta 105 zcmX@f{+fM)I4>6i0|N+yfbm3?3oP~j|NozC!E7`+lQ|qF0hB{mx_JTP3`P!+E@TbD RAOQ&Ave7m$wbM1_0sxE76nFpt diff --git a/libs/common/pytz/zoneinfo/Asia/Aqtobe b/libs/common/pytz/zoneinfo/Asia/Aqtobe index 7504052a7113e5fd0061f08aae1434461123e2bf..3b5d6eb41883a7b32819f387b4c03c93e9d489cf 100644 GIT binary patch delta 68 zcmeC=c*;IOoRsy0|N+yfYd~l9G3e3|Nl>%AUSbG08E&H0bSi>8O947ARP$BLLe>} OaM@@ZnA+)@asdD%QWR+b diff --git a/libs/common/pytz/zoneinfo/Asia/Ashkhabad b/libs/common/pytz/zoneinfo/Asia/Ashkhabad index 8d9e03c13ae19703ebe48105d418622e53a14b55..2bd1cb3da0f5a11024f1609b09ae68645e6ae75c 100644 GIT binary patch delta 109 zcmey%a+hU-I4=VbaDoURkesNJBf-eb1c5B9NRW+v;@SXo<&(7-FW^(p$z`K$U}~pp G$^`(JoeRqV delta 98 zcmcc1@|R_TI4>sy0|N+yfYd~l9G3e3|Nl>%AUSbG08E&H0bSi>8O947ARP$BLLe>} OaM@@ZnA+)@asdD%QWR+b diff --git a/libs/common/pytz/zoneinfo/Asia/Atyrau b/libs/common/pytz/zoneinfo/Asia/Atyrau index 317466d14b4749538893135618839a4c747838b5..e7ea9c545a56ae0b0fabd02e47748004a6d87c3b 100644 GIT binary patch delta 68 zcmey&evy5GI4=VbaDfOQFq)`xVd4*?$=Zxj=rWr(GtOXy%5iepXd9T?>6&r@0Pjx@ AX#fBK delta 105 zcmcb}{+WG(I4>6i0|N+yfbm3?3oP~j|NozC!)!D;mpK|H0hB{mx_JrX3`P!+E@TbD RAOQ&Ave7m$wbM1_0sxnG6r=zE diff --git a/libs/common/pytz/zoneinfo/Asia/Baghdad b/libs/common/pytz/zoneinfo/Asia/Baghdad index 97fa6c73cff714bf9858c9697185f300a1b51347..c0e607234a07cd2650aabc1fd022bd5d4f99b175 100644 GIT binary patch delta 70 zcmaFNev*BHI4=Vbuz?65Fq^3IU@`}z*<^c0D|D&Niy3z?!X!DlY_tuG?R1T~0K$$8 Ag8%>k delta 97 zcmX@f{+NA&I4>Im0|N+yfcZp~2Q2mf|NoyHz-%_zlF=F}fv#-x48|Rd93UOYiupkT P3=CX0+6KmUy2e}p391wz diff --git a/libs/common/pytz/zoneinfo/Asia/Bahrain b/libs/common/pytz/zoneinfo/Asia/Bahrain index f5140926a2fb09bff7636139e0aa450d5ae06f9d..098997e7dd972ca4acedc535c4a645ee966baa33 100644 GIT binary patch literal 185 zcmWHE%1kq2zzdjwvdlotv+&bg1D38y2N;1O1_2;F77h$73=CQZ3>-ebAq?6ECO~Y= a5JG~P|A7ExF32=u%;vHI+F++^%mo0p>ljY} literal 211 zcmWHE%1kq2zyQoZ5fBCe7@KF|r@00!U6T&f|NsA=k%@_c!5{!6Z{fhe!oZ+qz`)_- p8^WM%U;@O(APFD{A;EZ{ssBM%fb@Z^0nsE_#bpDu)=t-$3jiUoA6WnZ diff --git a/libs/common/pytz/zoneinfo/Asia/Baku b/libs/common/pytz/zoneinfo/Asia/Baku index 8a090d77d04e49ad6f25cc9cc0d1b050a67233b0..ae0ce4e7c3d273b537fa3653784e73be68ebb441 100644 GIT binary patch delta 89 zcmaFPxtDW-GNaQ(l>!dK*qZ`IMOy?WE}1defl-QygL85si_GK#My|i_@% delta 175 zcmdnX`J8itGNbE6l>z~SXRi+!#@-Y#D%v7obh6;U#5pq*>;M1%&&bRKfh?>@kd2*< zg_(nMav-BB6BF~~QpOfW*U7&a@5q3RVE_Vy|CYU5AmyNc8xt*>#7XYs! B3-JH| delta 108 zcmdnad6RR3I4>6i0|N+yfXhY|5k{8!|Ns9_wqSIgT*Vj;lL1OjzQ~BCdh=IC1tt!V VMr19*AOQ&Ave7m$x6?J}0sy5z6w&|y diff --git a/libs/common/pytz/zoneinfo/Asia/Beirut b/libs/common/pytz/zoneinfo/Asia/Beirut index efb24c27f83894eb39286eeb571eb064ca27fb5f..fb266ede2279b6aff913538d9d5aae3935e53aeb 100644 GIT binary patch delta 35 jcmew+@Je8UI4c7PY?S%HG+Brl%G<2Rtin1uf-@%RkG$>{wTP96E*aN6c`!0Cw*43|7|KQJ;gp+FX928Q$k zkQ*{97+4q>N(~sf7#Io~7z7v?>LxIVK-fOMAq?6E=Eep<(!vN#0$G*}AtdfK%5DoGjm|Ns9pGBGhQWbXmVXI3zU}aNehq^5QLCm0npt4AWJ|r$Rd#bdLWx<%eZWS7Tf7sZ~*`+LnY$? diff --git a/libs/common/pytz/zoneinfo/Asia/Calcutta b/libs/common/pytz/zoneinfo/Asia/Calcutta index e1cfcb8d09dc8e16f828082396f5095a367881e7..0014046d29a38e9b8006f746fea794d7f71eb479 100644 GIT binary patch delta 37 jcmZ3_G?!_DI4c7POq7X$GiNk&ad`%Z=$cv?8*l*tj_C(y delta 56 ycmbQsw4P~#I4cVS0|U!MnFtOb4F*6lRv?3Q;)G@qs1T6i@(d2qHMKG}-~s^Srw4-o diff --git a/libs/common/pytz/zoneinfo/Asia/Chita b/libs/common/pytz/zoneinfo/Asia/Chita index 3baf7528268ac85f44ea7672ee0827901a302740..75b3d7b3a6b3bf2b7459532fce1142912b897efd 100644 GIT binary patch delta 71 zcmcc3xt()@I4=VbaDxaSaN4LMz&P20(P?ruV=TJdW_BhCCYU5AmyNc8rJb%N7XYot B3+Mm< delta 108 zcmdnad7E>BI4?H?0|N+yfb&Kb0Y;Yk|Ns9_=4Wx59KaF>lL5)1E8hH_QG$sBB!jF& R1S9|jTsGPUmUg<9TmWAN6K?O delta 133 zcmZ3^evy5GI4?T`0|N+yfXPIaD{S@u|Nm!Xnrz6d#m>Ua#0rFy$@6951J diff --git a/libs/common/pytz/zoneinfo/Asia/Chungking b/libs/common/pytz/zoneinfo/Asia/Chungking index ce9e00a5db7c447fde613c59b214e8070f30ba2f..91f6f8bc2e234bafd484146986bdb289082c3588 100644 GIT binary patch delta 111 zcmZ3;vXNzixFQ1*kOi`tftaTONGzDXs9|9x%ZG^u&6B?{Doha91q#9eT$@6951J diff --git a/libs/common/pytz/zoneinfo/Asia/Colombo b/libs/common/pytz/zoneinfo/Asia/Colombo index 4fc96c898acd87282afde27b9e729a54c34957e5..353fe2aa3564c79809e05e12711f90669e5e2388 100644 GIT binary patch delta 82 zcmbQj{ETUWI4=Vd;FzeQ!OqCU%)-RVHd&X^bK*vH`Kunt@_bx2+6Jb^26nopR>lTg E0KEYV6951J delta 118 zcmaFHG=+JB_{9AjtPDWFIZ;J}r~d!{|BOt`EKICyOq2N;Jq18A$N*XW0S~Y=x*j=X V^&mEvjkbZQv4Negsg!p6wN%*4VvG1w79@`wXMl9S6u+rZ3D*Nh7QUeyVh delta 111 zcmX@i^pa_UI4>Im0|N+y0LMfX74G`~|Nk>GF*C8SvP|@IAV&Wdk(PPS=+*fUZ)6`1k|F zMO;c1{{Rt0mn#n4%7F@k;N~Xc{nEw3!It!MBpLeqmi>FJhk*`_5P}PzQ+eUsQq*4* zXS%jTSZWuCdaWZCBupF1zG`P*Z@P`D7***age4#d*_B+rdm zVtHzWuY{>sO-|4?e^l6doZ27lBHtXO`AURsynoZpn>M!fDCqXdC)>ID;=6W*IY(8l zD&E%(s5v*J_Vf|b+lYTRU?_Ej1Vf}LX{JabF^-=Uo)F>|)y}`Cl&b2z=HMOdf!GAG z3u2q6+y}AIQ|^S=3b7YrGsJF)?GXDR8bEY_vW4T_KoHzO6o@boaXe)ph)A9?6hthD wU=YzD!a>A?2nc^gtff@kZZ(URqyK41d@}1`6oGYfI2;Sdh78?|7`2n{2edVp*8l(j diff --git a/libs/common/pytz/zoneinfo/Asia/Dhaka b/libs/common/pytz/zoneinfo/Asia/Dhaka index 776f27dac064a5925a2ca5c3cd4c3c60b0e4af37..3cf597d83f90403132573d78247e6d02e66d0583 100644 GIT binary patch delta 72 vcmaFKbeL&^I4=VdV4tX>!p6wN%*4VvG1w79@`wXMl9S6u+rZ3D*Nh7QUeyVh delta 111 zcmX@i^pa_UI4>Im0|N+y0LMfX74G`~|Nk>GF*C8SvP|@IzDOf;caV&WTMLJRor-mHZ$+0|S?hwt=Oct|b=$hol!i diff --git a/libs/common/pytz/zoneinfo/Asia/Dubai b/libs/common/pytz/zoneinfo/Asia/Dubai index 7880d5d7c92a4a5fd994630588e9d74fbd636edb..b3ac791aef4e73d6d644c40c614f37f15d462cdd 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCvGCK(6+n*h4UlpR2L=`&-w+0E0~3Z25)And1UL=ivH==r Ir)$Cm0Qy}Np8x;= literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^r!*##FZY%f|F$#Pe4RDxoorzOzm_{xd5h<4i*3a delta 98 zcmX@ea-U^_I4=hS0|N+yfaFA#43_%;|Nl?yk(m6R(Gw=lfUa(`0OJX;B$8r5h#3qF NTsGPUrgplfTmT!?6pH`= diff --git a/libs/common/pytz/zoneinfo/Asia/Gaza b/libs/common/pytz/zoneinfo/Asia/Gaza index cf54deb8f1d44eeb4281a3500415fdb1e4c7ff46..22c47593d4698743bc60ad98e60e45e3c48da7b1 100644 GIT binary patch delta 1104 zcmc(dOGs2v7=ZsfcYIArSX$(x7Zg(JSSm~r6x6f`q$r6jn{GZv%}3Kqr+mzzMCfJ7 zg@{OMA%;*SxK7;ESY-APTJ*q;MMQO15hB(3rl7z!t@;n=erL{|#W3IZDdo#tt7K1Y zus~*vN8hlS%X)K8Q~EJdIwm7iee%F}O+A|YphjCfpQk}WXseb2^nrXLF8%>vk zFW)+KebZ8Z&kQwmzLCb>$%5&d*#TNg^xr4R`FBETV5x?4HEzh+*izSqzPzC;j1H zBz@P@c6aLlL{^Mi7(^OG97G;OAVgv|2TVe2LX1MJLd@C^&GA*J<>#Uoq8OqXq8e)5 zcE9`eU{{>SK={@8B82k_=@9V{`H%o05kNwK!~h8b5(Oj-hB#u>fiOe@356jR7@-)3 zY9Qq>)B`Dqp(030keVPxL8^k31*t1WT^OXY7%q#R3<>wTy&i{4v`~VJhMn(Vt delta 1067 zcmd6lPe>F|9DrvQ*EG?}wGO307cZ5i;L;)N5G99;H@{z`+m%u!!X~sT>NuO;ABH< zsLJ|h<(enltdzN#xAvRuL;W)~G5gv#IlO9pdiN`n9t^3>BmL8D zDw7hC4W+MstFr40sqgJM-kXccIc_Htbw2;n8y)bBi@?_*UB5Ui8lEThsj1td@oq#n zjY-kmcTJvtcuR@qusqY-q0ZJEmFF6Ls%7VH89Z2}f~$oxTw0>SGxIXCvaDL)7wGe| z9?|w_PG5MtB-)c{-7)k{TnvqBIq*VUI^yWdEf2(%@=hH+aX2nIH{Fq4d+)2R`KXK) z_NZ9eFT3ZO)YYd4WY6Rw)jPOd#-Hp`eKD_2+^Z2Td+o~^&D-I3*>0}2-(2^pvyfNf zl;xKv%%8VxHp^O%u|H8f%z#>mUdNL&U4UP{$T)V2 z-7{IqV?W$QwyVzPtmIZFj8V?93NZ_@3o#6_3^5I{4KWU}o~1Dlv7eSEQBw`197sJ`nt~t|Wob%+)MQX=krWTe3X&EiFGynW cKgrC^x7dy^e_N4vU*(?K%Ka796=vG_7lNul-v9sr diff --git a/libs/common/pytz/zoneinfo/Asia/Harbin b/libs/common/pytz/zoneinfo/Asia/Harbin index ce9e00a5db7c447fde613c59b214e8070f30ba2f..91f6f8bc2e234bafd484146986bdb289082c3588 100644 GIT binary patch delta 111 zcmZ3;vXNzixFQ1*kOi`tftaTONGzDXs9|9x%ZG^u&6B?{Doha91q#9eT$@6951J diff --git a/libs/common/pytz/zoneinfo/Asia/Hebron b/libs/common/pytz/zoneinfo/Asia/Hebron index 09c876a669d3cf20a53a148dbf9320307b2f2127..0ee464803029ae4885814acae9e3291771401e62 100644 GIT binary patch literal 2450 zcmd_rYfM&k0LStF6UcSe$ZST)Me%}hc@!i_Nv2YZ@Tao>g0uz(N9@724dO~yt^M=cSH)24QLnvy ziuucd%l6g60~sQ3Duz2=SO4c?pKA?B^*Zu{0~mKdJk=Nv=|vqwSSwIVFc{h zqXO=}g5v1H3dX%rn$&w1MA z(#%F7}=WqMk>pDZl3l}fj z>zi+j%9B3c4fWrPjZK%lo7S8V-nvfji%WXUmnvGkn-h+kTQc`}Uw)|3+#2_Ry>)1p zSv7XIUDZ`#ZojhLe&tBES$#Iw-f^&8)O?g+@2tuawQq#jKCTD;xqN-@bNP+Ymw$kN z0N#7~_y#)H{eJX6yeC9tum0B;Gu$p;A;Ka>;9vK<@96tj=baGed;WgrI~V7AL{`=^ zZXS`7sbBh8xLo``m*(!1{=)MwhB&HEP5Yw9kU=`?Q6j@c#)%9R87VSUWUR}89Op~Wc0}Jk?|t|aMTe%Lg1)lfCK@F z0)JG(;Hcw(1OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CP+|_s32iM;(`RmQAY*|4H6q9 zI7oDm@F4L)0)#{e2@w(_M;#<2N=TTHI3a;TB87wsi4_toM;$FBTu8i-fFTh>LWaZ) z2^tbLBy33Bkia34Lqdnd&QS*si5?O@Bz{N$kq9CoL}G{p(NRayQHK$UBN9j?l1M0# zSR%nhqKSmlQO6SrsH2W35>h0lNKlceB4I`1iUbykED~BIwn%W1=px}o;>#hxjyl2| zLX5%XxTB6Z5^_f!b0p|U)RC|wapw?t kB=Vg9edvSqCqdjSJ`-Yd6XTQOlM-gTV{>P^|9lMk17sZn^8f$< delta 1096 zcmd6lO-NKh0D$LhyIG19nFnhXyF@>_>6VL!uuB^htt_-eMcP#EkD4}>%9^h0!GbIj zeH26;BM6FqJas4#q94e1E1^r$T%?ouqKqK0=6iGtggQ0Dyl z6G8FESZYpK4BIUBrGw`2;^0I5YGxujd1pbqe)ct=8%gN=?cV&GA*tUEzf|wy_Xnri zb$(Q)3j9yL>+%bqM?ZFWLl?hjd#`ob(-^fsSY_EODW_>%$>xD`>d1{tS~e%u(f&?- ztYNo0emJUIDmJV5_Mnb06sx3vwN6gYsnm~ey7hUH(>7Bg+i%S}C!WsBj`KOE^X7Yb zGV#DsLyzRCUAA+&<+?nxzT4^AnUUQq##B%BHQh7UrP9THI-T3EdS{z-{n>lAMEL+>=v3$(M-~5MLAu2zaUo1A&SY9Fgm14QwxMV|? zBa4R+e`D*X8JWj>${^Mt<{yD@3hb?wu;aEnj5(+J4`(D|md~ zS7Bkew4e^d++!JH8e$t_9AX_}9%3I-0Hgw!rUXb0E=>`TDj;P*>VR1iVM8X6R3N!P zl7VCcNe7Y-Bq2yfkdz=fxim>ZvT|wCg5+fo5hTU|nL$#6QjkbZgovt|-00Ji(Y5)KL diff --git a/libs/common/pytz/zoneinfo/Asia/Hong_Kong b/libs/common/pytz/zoneinfo/Asia/Hong_Kong index 8e5c5813666a4d023bc9f7f7bd0e53ca1a61dd85..f9f7b134dd5c35e4718c6fa3697024cb95442c53 100644 GIT binary patch literal 1233 zcmd7ROGs2v9Dwn2=b2L$4b`F+MNu3GYjV>usMH9nX>!qk5tu~~4cdz!TMb48LJnyhj~_eUG*MaWBS(BnDmND zlP*@7{+V-3|MLZAV5DGPm`U^Ue%fR%&sT%D^6J%z3HAEiUHPUyq23<5D&MV)s-dPX zot;&oa_c*FZhW&DUU*s$r-EU_QS+(0MUJ*UGN1J}`K95e`Pv?l z->M&~vHfA0Uvxti>c-^vrAakDcaJRkcdKH4gZwd6tA1vh^u&W*X7YB8{&lU|lrApO z;_PDMB>mcR&}Y1jIqi#Ojla$(1FO9%xGXD!C)cX7K({O_bg5AGik#ltsb<_cBWGWz zQsF}_+VOaOL61LRx986edZxIAXb$nE54Q=Q5XqEdKX+@pQ_fk0|9;LpTVrvtDay5- zTSHuIu?OMUpK$CA?TSxfv?aS3FEV0RJ7#3m$heV_BV$KKkBlEl0Lj4Brhw$&YLh^+ zK+-_+KoUVRK~h0-L6SkTLDE6;K@vhTa~nn<2VqDZFbrpkd_SvFZ9TO?g1UnF59V9sl#(L-rq0p+dg_>2eu! delta 553 zcmcb}Ih}KYcp@tU0|N+yfGZG#*n+K@QQuC!3g2<+meq>W-W3g(e3A+-XKlB*GTCdz zRi_OW*JkZpaox^w#|=G|6*qob?zqLrw&K=J=^eMEKr|>UKr|>kKr|>!z%(#i zKr|?9{{PnlGJpXD5fhz11|hz11~hz121hz14P=J`w$7za4mxY&R}scXRn0CdE| AC;$Ke diff --git a/libs/common/pytz/zoneinfo/Asia/Hovd b/libs/common/pytz/zoneinfo/Asia/Hovd index 8eb5f647969380c5645d1af2ca645f70185b1209..8b9abca344daf199b554888fc4fff562a7a18e4a 100644 GIT binary patch delta 82 zcmeBXf6F#OoRHZZr-HRl2V DB!LYm delta 111 zcmaFM*3CXaoR@`xfdPa;z<8p{1=jli|Nk>i7G&0*EWpS%*_v6C10u=5fUbIT5#tO- VPLM2y9zmEME*otFb30vgE&#fa6gdC@ diff --git a/libs/common/pytz/zoneinfo/Asia/Irkutsk b/libs/common/pytz/zoneinfo/Asia/Irkutsk index e8c53c0d63d4ab9cc7b7c95597e4cb490e782cce..e74a4d3f6b3a799b0fd12272c3aae0f084fb2783 100644 GIT binary patch delta 71 zcmey&d6sj6I4=Vb@PG&)aN4LMz&P20(P?ruV+Ok1W??1?CYU5AmyNc8g`KVi7XZEM B3^)J) delta 110 zcmX@h`I&QqI4=(a0|N+yfb&Kb0Y;Yk|Ns9_mSS<59Lj}N4=@BVvc%QZoFz- zT$wDNKQhv0-`q>Kcz-?33OAPVa5Bo?DyK$ew#2IQHsifJY}pnBw~uUd`_~e2>dV|| za_3Hni!eP9Pn1zA zqDqLWBBl7nrs8OEIR6|?oGIF|9LL|xo1NLM)!od^RxJfJArDzeXotwdB3cU%+@ptg5HzGvBnTm9pb$M+ z?LGsc4s!_EQ$%~xOXEfShdlpW!WHJ?DJ+%j(qVev0Z-liJ>GnV-{ryQGm|(>zH8E6 z<(s?f?4NukSdy!Kr+jVGVe9Lk`NqK#+Zy}f=U=qgzlwZsdw`X9CEqW-mB+sq_{rd+ zJRLUqpN39J+(lm-DM{V==+j5@VT^O+d6x{8kV_c@8B=iS)eegDFdCF99W z9{=9VDn51b%K1D?ysqQP$#GUSH0NE;-XqmL6JDxm-?`E{;MLG?PW4smftQ}1W$E&) zSHHX?uYE|df>ty#b9a1E)RTxzldezNj`tiw;w)PHn?~12ZjVSBNSb@I0sEnyM16UJNv_1}9 zSU{O@X_hKy;)tefiR{CP(v@l!2 ObfehX%E395Yy1bA+v^el diff --git a/libs/common/pytz/zoneinfo/Asia/Jakarta b/libs/common/pytz/zoneinfo/Asia/Jakarta index 673d48010de39ac1eeb2fac8fbb08209bd0f98c8..ec4bd5747a8c9c528dfd22c8e3171851784ade59 100644 GIT binary patch delta 44 jcmey*^q*vTmZ~b2W0>N diff --git a/libs/common/pytz/zoneinfo/Asia/Jayapura b/libs/common/pytz/zoneinfo/Asia/Jayapura index a4c0829721cf845e1bf08332503e1fe742dca5fa..3002c82022f7e9a4ff1ad545ea617106a13b24e0 100644 GIT binary patch delta 34 fcmaFMc$aa4I4c7POqB72Gdn}sxxzg|bS=36g?0x2 delta 51 ocmcc1_?B^kI4cVS0|U!M8BY!%4F+(r#!!Bk7+1Jwh^{3U0Mpe67XSbN diff --git a/libs/common/pytz/zoneinfo/Asia/Jerusalem b/libs/common/pytz/zoneinfo/Asia/Jerusalem index 2d14c9998e9dc54bd1ad4710332abafc8e811242..1ebd0664aa29c0abd722661f761031ec0304631c 100644 GIT binary patch delta 923 zcmd7QPe_w-9LMp`v-aoFJgEH=V4#CiD(FyDw5XFZ{B!VuXr~YlVjvs-AP=Ib z!9%w&2pI?pi$>68L4jMK?okKRrEUJJ)h?ZE4DIRnSzfz%2%m@F@BMwA8{aPfIA7w0oVZjj_{h z;uG5~-PoDDD|XM`z}LQ?_*Stjvh}kfn;n4aAI+!-A){pPhgKTAWSsiGubr;#(#{p; zwDaRVnlqBmTzKMt9et%(>=u2~;Tp8rZFYz1P}%?U0=0H|XmI6Wh3;3Yt^!UM%76y9 zqW+!lKWM`}xjeVzYGvgBD2l@EZ~*>=V#>$+_CtXs4oje7B@#<0mRKyoSfa6nV~NKS zkR>8ZNYlJyn$+YEiWVzXS<151Whq>&RAwp7Qd?K8jn{mFpY&yE%+i^qHA`=n=EX{P nz1LcIk=Q&}g0n^NujOF-gy7WEPn}c14GMN^F$T_)vu$Pu1^Vv~IgD z>$+K@H2a!(4zOfUB3HX2yJidICKy&yYkLawOpx1wX>u2G%4tWNMDLeBNyciW593kx zXo)Dnm(T7lJ$046UwpLxwoy(m#aT(y{N1$pY2zS@Ghu>Z;zktb1i42@=V;QvW@ Pi-6JCssE2LB$N09QcoF* diff --git a/libs/common/pytz/zoneinfo/Asia/Kabul b/libs/common/pytz/zoneinfo/Asia/Kabul index a22cf592553c4bab6ef93cf34fc304162999b655..661efc83294edbe5f615daa87a1f4a5590aae167 100644 GIT binary patch literal 194 zcmWHE%1kq2zzdjwvdlot*8n6g{JieK2o!O60OT-OI54m1X&HsAsPuvH(N literal 220 zcmWHE%1kq2zyQoZ5fBCe7@MyF$hq+Ix3uw9sorcEF2hE7#Qq57&v@< xLm0FTOhDM!03-zjAtYD;H1~gI+pGm38e|bje?5>*v}Ig2V5{wPO{|O!xB#TGCZ+%Y diff --git a/libs/common/pytz/zoneinfo/Asia/Kamchatka b/libs/common/pytz/zoneinfo/Asia/Kamchatka index b9ed49caca4bd81731958cc54adaa45bf84ee2f0..99776f515fd53a86a6886311d91ff33b401396c3 100644 GIT binary patch delta 64 zcmZ3$*}yqLeDWPe4ps&raM-BA!8qA~(P45Sqd&6v=C_O-OfU&9E*ot_BRgF~BQ5{| C{toH@ delta 105 zcmZqRT);U&oR^bo7V@&SngN$uKaWE8Tpbk%Nf?qzhTS SFh~FlxNNiyjqG#{jko{~kP{{V diff --git a/libs/common/pytz/zoneinfo/Asia/Karachi b/libs/common/pytz/zoneinfo/Asia/Karachi index 337e1d58620bad1d24a1e273c34d75e4071c1bdc..ba65c0e8d31cb1d59935878af29baac722f45936 100644 GIT binary patch delta 34 gcmbQt{F`ZlI4c7POqA(>GtX>c=L+x+(KY1)0Ek=%o&W#< delta 59 qcmey(G?{sVI4c_i0|VPcnGPNx4F_=X16zdQfFb;1VjD<0ZxOsY=Fku I>6&o?0LBIr)c^nh literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz diff --git a/libs/common/pytz/zoneinfo/Asia/Kathmandu b/libs/common/pytz/zoneinfo/Asia/Kathmandu index 2f810b1202a0edf2f9d0963cdc5004246369eeb8..751cf4a8939e898f8abe4358a98b2a5f59f65e21 100644 GIT binary patch literal 198 zcmWHE%1kq2zzdjwvdlm%u<%oDi=2eP3Pzxa{~wSZ-v|a428Mt;3|v0GAq?6Erp5*! g(!`V@gamW{0|Cfvkcq^a&t(I)$4=MO%EXil0OIH!VE_OC literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z diff --git a/libs/common/pytz/zoneinfo/Asia/Katmandu b/libs/common/pytz/zoneinfo/Asia/Katmandu index 2f810b1202a0edf2f9d0963cdc5004246369eeb8..751cf4a8939e898f8abe4358a98b2a5f59f65e21 100644 GIT binary patch literal 198 zcmWHE%1kq2zzdjwvdlm%u<%oDi=2eP3Pzxa{~wSZ-v|a428Mt;3|v0GAq?6Erp5*! g(!`V@gamW{0|Cfvkcq^a&t(I)$4=MO%EXil0OIH!VE_OC literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z diff --git a/libs/common/pytz/zoneinfo/Asia/Khandyga b/libs/common/pytz/zoneinfo/Asia/Khandyga index 2b2f5bfae4bfdbe898e256cb2d148b8bd17bdcc2..7cdc99a9803f4e1ce3c5f7f9e6cd761f178536ae 100644 GIT binary patch delta 63 zcmbQp^^$Xf_+)-24ps&raNejQ!Z_K6(Rp$MV?MI@W(6h%CYS^#myNc8rJb%N7XZQm B4PpQQ delta 112 zcmaFKIgx9EI4>^)0|N+yfXhY|5k{8!|Ns9_)?;y=T)#7XX(7 B3q=3` delta 108 zcmZ3|Ns9_Hehs|T)-FylL5)1E8hHxQGkg9B!jF& R1S9|jTsGPU=61T~TmWW)6R-dP diff --git a/libs/common/pytz/zoneinfo/Asia/Kuala_Lumpur b/libs/common/pytz/zoneinfo/Asia/Kuala_Lumpur index 6d7d47b9df2426dd083c4d420cfa33ecc0667bc7..adb41cf4caca6096052d7a82cf6adb568f3cee3a 100644 GIT binary patch delta 72 zcmbQw{E=yb_{1Y36SWn27@3$^SlL+F85j~{C&v1K#W@xJ13{fx@ZyPYEznhPa@lAb KSlH=WZ~*`b%@gqe delta 149 zcmey!G@p5bI4=hS0|N+y0Ov#%4Z-^V|Nk>GF|)9;v9hx>FvN#VmSOY}fG9wa6U1dv obOL2SdIm0|N+yfbc|>0I~Z2|Nk>GGoe5h=E?nxw%ia^U~=L?2{f(GW=Mf# UaBCMu($2tTqitYer)$9l0Huc$J^%m! diff --git a/libs/common/pytz/zoneinfo/Asia/Kuwait b/libs/common/pytz/zoneinfo/Asia/Kuwait index b2f9a2559a1ca4306a43c2abf074e91d51a64edc..8c8062471dce91a5be827d6908795ee7391a4afc 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCaaG!E3y`CI2BciefPuxwH-tgkz?dO~1VjD<0ZxOsY=Fku I=^AqZ09*+YLjV8( literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- diff --git a/libs/common/pytz/zoneinfo/Asia/Macao b/libs/common/pytz/zoneinfo/Asia/Macao index d801000dc3d598e39832d263358920f05928b337..cac65063d0dbf48e37c547fba3b67f34110d5a90 100644 GIT binary patch delta 33 lcmcb~d75*A_~dtt92@1gGD29Je=%NUV&`%W4$-yX0s!1Q3yJ^$ delta 44 ocmX@jd6RR3I3xQ;i9L+$3=ja~Z+^pggNY9+z~vkqqHDng0N=|9jQ{`u diff --git a/libs/common/pytz/zoneinfo/Asia/Macau b/libs/common/pytz/zoneinfo/Asia/Macau index d801000dc3d598e39832d263358920f05928b337..cac65063d0dbf48e37c547fba3b67f34110d5a90 100644 GIT binary patch delta 33 lcmcb~d75*A_~dtt92@1gGD29Je=%NUV&`%W4$-yX0s!1Q3yJ^$ delta 44 ocmX@jd6RR3I3xQ;i9L+$3=ja~Z+^pggNY9+z~vkqqHDng0N=|9jQ{`u diff --git a/libs/common/pytz/zoneinfo/Asia/Magadan b/libs/common/pytz/zoneinfo/Asia/Magadan index b20cc57efc71570ad78cd8060e28141336229dce..70c198baf743457c58a8fe4d1f28186d8541a505 100644 GIT binary patch delta 72 zcmcb^xr1|pI4=VbaDxaSaN4LMz&P20(P?ruV=TJdW_BhCCYU4_myNcep`EUwAr}C; CUknWZ delta 109 zcmdnNd53d?I4?H?0|N+yfb&Kb0Y;Yk|Ns9_=4Wx59KaF>lL5)1E8hH_QG$sBB!jF& S6eIuzTsGQDvMt@}S&2Jewm|zl|TsGPU=61T~TmTTs B4?X|@ delta 104 zcmey*F`sjSI4>sy0|N+yfa69L4n~&x|Ns9_)?sv*oXr>jlVM;$SGxH=BL@=)NEfnt SA&>wVaM@@ZnA_=^a{&M@854Q{ diff --git a/libs/common/pytz/zoneinfo/Asia/Novosibirsk b/libs/common/pytz/zoneinfo/Asia/Novosibirsk index 95e3c73bcc7aef0eabeca2d943eedd6dc1747fb0..4ac7582ad5ee895bf9b354436142fd7ece1996ee 100644 GIT binary patch delta 71 zcmcb~xt()@I4=VbaDfOQaNejQ!Z_K6(Rp$MV;H*JW)>y|CYU5AmyNc8xt*>#7XYs! B3-JH| delta 108 zcmdnad6RR3I4>6i0|N+yfXhY|5k{8!|Ns9_wqSIgT*Vj;lL1OjzQ~BCdh=IC1tt!V VMr19*AOQ&Ave7m$x6?J}0sy5z6w&|y diff --git a/libs/common/pytz/zoneinfo/Asia/Omsk b/libs/common/pytz/zoneinfo/Asia/Omsk index d805e4f74053b65018bd8a13cffe9fdbb0421928..16c5f3cfed75151d50ffc2dc483ac6279816a01f 100644 GIT binary patch delta 71 zcmX@hxsr2&I4=VbaDxaSaNMZE!#G)r#c^^BODwwFW+o;9CYU5AmyNc8nVqf~7XX(0 B3qt?^ delta 108 zcmZ3|Ns9_Hehs|T)-FylL5)1E8hHxQGkg9B!jF& R1S9|jTsGPUW_G$}TmWWz6RrRN diff --git a/libs/common/pytz/zoneinfo/Asia/Oral b/libs/common/pytz/zoneinfo/Asia/Oral index e36aec475db6fbd8c4fc79bc03332a8fca64bacf..3b9ecacf6eef72366df1cc847c0d4666efc8a48c 100644 GIT binary patch delta 70 zcmZqVxX(U8oR-ebAq?6ECO~Y= a5JG~P|A7ExF32=u%;vHI+F++^%mo0p>ljY} literal 211 zcmWHE%1kq2zyQoZ5fBCe7@KF|r@00!U6T&f|NsA=k%@_c!5{!6Z{fhe!oZ+qz`)_- p8^WM%U;@O(APFD{A;EZ{ssBM%fb@Z^0nsE_#bpDu)=t-$3jiUoA6WnZ diff --git a/libs/common/pytz/zoneinfo/Asia/Qostanay b/libs/common/pytz/zoneinfo/Asia/Qostanay new file mode 100644 index 0000000000000000000000000000000000000000..f8baf676496adea298f078d6d5931ba7aa64e902 GIT binary patch literal 997 zcmd7QJ4{ny7>Dt%UE1xhJPrP_X65t6*PTxayOmnD&0Viv>ezSM1KUpfuOFKoU%pp64_v3~!*VtJwyfQ6O4_rr zsJ&0`Xy58hbskLU<-(v|nG5UHbgN#A*0ewTO|OTJ)NKlh`}0&H$0x;sl}d5o)1eH$ z|0vO&eHq%?me})`a-;lAhL_4RvQU!I`Av;Ke57O3YdU^=UK2eFnvBnAvX;|SxFD&| z5uG^6$mF}AP8~Rse&x#ao?kMLTV&?Nv1IRkmD#dUpTD88!Eeu|=DPDjpzcfk4g0yp zn5ixPl2q0-8j}+fFeYF0weoK4d;OK1`0U;DU!LFPmvguBCOR^1UkUq4@-Sn3-jV-@ z4rlhD1=543&;;p%wDGikkVZ%+q!rQ&X@+z|+9CarhDb-GCDIdVigZQVB7Hq=W27_E z+SB$%nj_tj_DFwZ1IP}LEg*Y9Hi7H{*#@!?WFww-C&*Sj?Ou@0AiF`fgX{;{5V9j= TOURz^f810sZAX@gC4;{Ka(dML literal 0 HcmV?d00001 diff --git a/libs/common/pytz/zoneinfo/Asia/Qyzylorda b/libs/common/pytz/zoneinfo/Asia/Qyzylorda index 00b278440592895901730ba41ae6fa14b6f94107..27b522a7d5e24eafdf29dd541ebbca69ce4db7b8 100644 GIT binary patch delta 112 zcmey#{+WG(xF7=%aDxaSFacuji5eGJVx(^@nE1z-k!i94qYWDagIfRt$K)JFX>95@ V?_gZOh*JkAmyNc8shzGV7Xaid768oT9-4Vj!uO0P!J!%SPM4%-FzA*UZY;fC~VnOBm1q diff --git a/libs/common/pytz/zoneinfo/Asia/Riyadh b/libs/common/pytz/zoneinfo/Asia/Riyadh index b2f9a2559a1ca4306a43c2abf074e91d51a64edc..8c8062471dce91a5be827d6908795ee7391a4afc 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCaaG!E3y`CI2BciefPuxwH-tgkz?dO~1VjD<0ZxOsY=Fku I=^AqZ09*+YLjV8( literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- diff --git a/libs/common/pytz/zoneinfo/Asia/Saigon b/libs/common/pytz/zoneinfo/Asia/Saigon index eab94fe8985949b5d98cd003d26d9c2e70e2d0d6..a213d290e1a920271e924506a57191f1f4bfc4c0 100644 GIT binary patch delta 116 zcmey)bdhO-xFiD--~_VSfLOGn$GK~wfdVfh6Eh1FGY~Q`B!8Q%%V;`5Toy?^16;#@ WAOLH+?trO@lgmciz}!yPoC^RePZUuA delta 153 zcmcb}^qpyfxFj0`0|N+y02dI0*rFXh&J7a{6ol&k|Nqa(#LU9P41`P!3@Ho~V@

QjkbZgovt|-00Ji(Y5)KL diff --git a/libs/common/pytz/zoneinfo/Asia/Sakhalin b/libs/common/pytz/zoneinfo/Asia/Sakhalin index 9c94900ce88f7f653b21687cb8118ff3fca27a3c..beb77b449654decdfec1f6bfc16fafc8bc8ceeaa 100644 GIT binary patch delta 72 zcmX@YxrB3qI4=VbaDoURaN4LMz&Kf&#c6UJOE9|J=HHAGOfX3sy0|N+yfb&Kb0Y;Yk|Ns9_W@T}j?8XuTlVM;$SGxHvqXZKNNEfnt TVUPeAaM@@Z8rta^8gc;u8(b3> diff --git a/libs/common/pytz/zoneinfo/Asia/Samarkand b/libs/common/pytz/zoneinfo/Asia/Samarkand index a5d1e97004ff763fab160e6c8a2bcbfad7cf67fc..8a93767bfef2cecbeee15db413a0a7c277e761a1 100644 GIT binary patch delta 109 zcmcc1vYBOqI4=Vbu!9I7keH~FA;HMR%)-nBgsf~RaN;^gbmfz!7*F6+&&g$@ZD4Ar HYsv)xbG{2* delta 114 zcmdnYa+hU-_#{RaPId+ekeaBH!J(P3&!GPQ|NoOOGD6&r@0QTn@0ssI2 diff --git a/libs/common/pytz/zoneinfo/Asia/Seoul b/libs/common/pytz/zoneinfo/Asia/Seoul index fa1cbd3952c362552f524061301b11f03770f335..96199e73e73aafacd89e48cb2855a96d7a134e1d 100644 GIT binary patch delta 283 zcmZo=dC4+C+>rqZ$O75yKrGOG{X@m1XJS7tIjeuTp4wk<{oUMz8$W&(+&ruM;MVo7 z54YFyKe+Sk(TBS|A0JG#@G@p$hC)VGBnTE`U?}GRSyA4>z{0>#H-Q1j>si3aGx-9e z(gblqkQ5Ant)KW;!3JdIe;@!^3!*_*gJ_WTAR6QZ5Djt$hz2(=S^dMr2rpG;MiwN<%nF1I4CNdk zJ>?w?EDQ{F6BsxqJ1{BnfP`RRg18`t>67Id6(-j+_7QKSFw8g*&E*{&qHD$@6951J diff --git a/libs/common/pytz/zoneinfo/Asia/Singapore b/libs/common/pytz/zoneinfo/Asia/Singapore index ebc4b0d9d2ade7381506bc35d054f361f8a942d9..adb41cf4caca6096052d7a82cf6adb568f3cee3a 100644 GIT binary patch delta 52 vcmbQw{E=yb_{1Y36SWoD8JU<_SlL)7Mg&5Ho;V^2adO#c8(7%sT5tgXcrOf& delta 123 zcmey!G@p5bI4=hS0|N+y0Ov#%4W9b{|Nk>GF|)9;v9eDVXABg8$RS7!O(#6SYS1-G RBD6qQTsGPU7IwN8TmW|Ns9_Hehs|T)-FylL5)1E8hHxQGkg9B!jF& S6eIuzTsGQ%oRt9tHp*}_PBvtO@;3W2u4iQDat;pBwcr8(mXHS% delta 58 ucmey#+RHXUoRyVlp=Mq6`dN&cPwN7F+=6y$1XM diff --git a/libs/common/pytz/zoneinfo/Asia/Tashkent b/libs/common/pytz/zoneinfo/Asia/Tashkent index e75bb365a78e6dd0a7ce4d75c8d36ccdb8f7f7c4..a9f6cd93c849c8b1677bc54f8bb836c91b40d63d 100644 GIT binary patch delta 109 zcmaFMa*$<$I4=VbaDWIPkeH~FA;HMZ1c5B9NRW+v;yMp><&%{dPvBF}$z`K$U}~pp G$^`&_I}3RL delta 116 zcmX@e@|I5J8^2ZBqI|GGX%1-AwhPI$#RU+ldTw&(Dh9|&3JY4 U3Z~11b#ijqXd9T=>6&l>0B@=hHUIzs delta 179 zcmey%zMf-(I4?H?0|N+yfZ0TqT;_yZ0TUnG>QHNKT%>D2Zy`!RpmFMez*{Q^!L@rl zCVKp-XJlq#WnpDzq7X7L7+8Ru03=x$80qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSmmjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)56jzBYpzV*O<5f diff --git a/libs/common/pytz/zoneinfo/Asia/Tel_Aviv b/libs/common/pytz/zoneinfo/Asia/Tel_Aviv index 2d14c9998e9dc54bd1ad4710332abafc8e811242..1ebd0664aa29c0abd722661f761031ec0304631c 100644 GIT binary patch delta 923 zcmd7QPe_w-9LMp`v-aoFJgEH=V4#CiD(FyDw5XFZ{B!VuXr~YlVjvs-AP=Ib z!9%w&2pI?pi$>68L4jMK?okKRrEUJJ)h?ZE4DIRnSzfz%2%m@F@BMwA8{aPfIA7w0oVZjj_{h z;uG5~-PoDDD|XM`z}LQ?_*Stjvh}kfn;n4aAI+!-A){pPhgKTAWSsiGubr;#(#{p; zwDaRVnlqBmTzKMt9et%(>=u2~;Tp8rZFYz1P}%?U0=0H|XmI6Wh3;3Yt^!UM%76y9 zqW+!lKWM`}xjeVzYGvgBD2l@EZ~*>=V#>$+_CtXs4oje7B@#<0mRKyoSfa6nV~NKS zkR>8ZNYlJyn$+YEiWVzXS<151Whq>&RAwp7Qd?K8jn{mFpY&yE%+i^qHA`=n=EX{P nz1LcIk=Q&}g0n^NujOF-gy7WEPn}c14GMN^F$T_)vu$Pu1^Vv~IgD z>$+K@H2a!(4zOfUB3HX2yJidICKy&yYkLawOpx1wX>u2G%4tWNMDLeBNyciW593kx zXo)Dnm(T7lJ$046UwpLxwoy(m#aT(y{N1$pY2zS@Ghu>Z;zktb1i42@=V;QvW@ Pi-6JCssE2LB$N09QcoF* diff --git a/libs/common/pytz/zoneinfo/Asia/Thimbu b/libs/common/pytz/zoneinfo/Asia/Thimbu index 06d3324d057d43c8e3bcc64069e95670861e3c9d..95a9de9657f5b8b15aacb6e05c085c7e11bb8f55 100644 GIT binary patch literal 189 zcmWHE%1kq2zzdjwvdlotclDWQiDIJf3Pzwv2oFe)Zv+Dike$H5<>MQ|plx7kYyc$9 a7(z%e^*<1ROa_@poatOPKs)So&A0#reHiZm literal 215 zcmWHE%1kq2zyQoZ5fBCe7@P0vGtm;oMBf$l|NsAIWMX1q2;l+A`$jOZ0NDu)Tt2=b q4B7^!#s)yr3?u~vAtYD;H1|Kq5|BQSMIf3a%eZWS7Tf8XaRC5@Mj!bA diff --git a/libs/common/pytz/zoneinfo/Asia/Thimphu b/libs/common/pytz/zoneinfo/Asia/Thimphu index 06d3324d057d43c8e3bcc64069e95670861e3c9d..95a9de9657f5b8b15aacb6e05c085c7e11bb8f55 100644 GIT binary patch literal 189 zcmWHE%1kq2zzdjwvdlotclDWQiDIJf3Pzwv2oFe)Zv+Dike$H5<>MQ|plx7kYyc$9 a7(z%e^*<1ROa_@poatOPKs)So&A0#reHiZm literal 215 zcmWHE%1kq2zyQoZ5fBCe7@P0vGtm;oMBf$l|NsAIWMX1q2;l+A`$jOZ0NDu)Tt2=b q4B7^!#s)yr3?u~vAtYD;H1|Kq5|BQSMIf3a%eZWS7Tf8XaRC5@Mj!bA diff --git a/libs/common/pytz/zoneinfo/Asia/Tomsk b/libs/common/pytz/zoneinfo/Asia/Tomsk index 28da9c901f23e9a3b6892236ead6f86e311fe1af..a6e494a78cef4baaa30d06cea4186391788eb30b 100644 GIT binary patch delta 71 zcmcb~xt()@I4=VbaDfOQaNejQ!Z_K6(Rp$MV;H*JW)>y|CYU5AmyNc8xt*>#7XYs! B3-JH| delta 108 zcmdnad6RR3I4>6i0|N+yfXhY|5k{8!|Ns9_wqSIgT*Vj;lL1OjzQ~BCdh=IC1tt!V VMr19*AOQ&Ave7m$x6?J}0sy5z6w&|y diff --git a/libs/common/pytz/zoneinfo/Asia/Ujung_Pandang b/libs/common/pytz/zoneinfo/Asia/Ujung_Pandang index ed55442e2917b4bbccce34f3e1c531d1f3284ac4..556ba866933d37f3cfcf8042045d64e209bae30f 100644 GIT binary patch delta 35 hcmbQl^pA0ZI4c7POq2Hn6bMwcr8( DB!~?v delta 111 zcmaFM*3CXaoR@`xfdPa;z<8p{1=jli|Nk>i7G&0*EWpS%*_v6C10u=5fUbIT5#tO- VPLM2y9zmEME*otF3p-s4E&#fh6gvO_ diff --git a/libs/common/pytz/zoneinfo/Asia/Ulan_Bator b/libs/common/pytz/zoneinfo/Asia/Ulan_Bator index 82fd47609e125ee799e3d3be4489d6cfd4782abb..2aa5cc4b84d369b03684c30cdb66b7abf087f467 100644 GIT binary patch delta 82 zcmeBXf6F#OoRHn6bMwcr8( DB!~?v delta 111 zcmaFM*3CXaoR@`xfdPa;z<8p{1=jli|Nk>i7G&0*EWpS%*_v6C10u=5fUbIT5#tO- VPLM2y9zmEME*otF3p-s4E&#fh6gvO_ diff --git a/libs/common/pytz/zoneinfo/Asia/Urumqi b/libs/common/pytz/zoneinfo/Asia/Urumqi index 0342b433180b416a02c5ff8764e7a0a57921091b..62bdcac14db3f464ff561e32db2c6b55c0cb1866 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCvEkpY6d)%^2BbVBfq})xH-tgkz>Fb;1VjD<0ZxOsY=Fku I>6&o?0LBIr)c^nh literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz diff --git a/libs/common/pytz/zoneinfo/Asia/Ust-Nera b/libs/common/pytz/zoneinfo/Asia/Ust-Nera index c0c3767e38042e4d9d1d7c2bb102e1ffdaa2602d..d05726aba9fd67bf230290b3e2d74b75e46ee214 100644 GIT binary patch delta 72 zcmeyvd5v>|I4=Vb@PG&)aN4LMz&P20(P?ruV-C99W^pD7CYU4_myNceft{|Q0T%$! CbPRa_ delta 111 zcmcb{`G<3YI4=(a0|N+yfb&Kb0Y;Yk|Ns9_mSb_69M6&qlL1SktKQ7UB*DZ1l0w!Z S2389txNNiy4eWFc4Y&Z50~0_1 diff --git a/libs/common/pytz/zoneinfo/Asia/Vientiane b/libs/common/pytz/zoneinfo/Asia/Vientiane index 7249640294cd596c9445ce175455efa06b5dc6c3..fa799db39e7625dd74bd9caa5c29b4819a7cbd3f 100644 GIT binary patch literal 185 zcmWHE%1kq2zzdjwvdlot(*PtEWu+fs1d7Dj068F%g@GZdfPuruH-y0nh_wyO8A3=f Y^FI)TZEHOOqKPq^%LZtJovt|-06W1QE&u=k literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% diff --git a/libs/common/pytz/zoneinfo/Asia/Vladivostok b/libs/common/pytz/zoneinfo/Asia/Vladivostok index 15731abc81dfb016221260ed6ae40af24aefb89e..274a10b43d8089ed522daf9ded5b6aa3653075c9 100644 GIT binary patch delta 72 zcmX@dxr%dwI4=VbaDxaSaNMZE!#G)r#c^^BODwwFW+o;9CYU4_myNceft{|Q0T%$H C0t;*a delta 109 zcmZ3*d5&{}I4?H?0|N+yfYU}59!8e>|Ns9_Hehs|T)-FylL5)1E8hHxQGkg9B!jF& S6eIuzTsGQ<26noJ23!DpKoi#h diff --git a/libs/common/pytz/zoneinfo/Asia/Yakutsk b/libs/common/pytz/zoneinfo/Asia/Yakutsk index 1f86e77f5806a6c7a19143071de4fd72145bf037..ae65a5f9b94d196b29b68ddec7862e098d829b59 100644 GIT binary patch delta 71 zcmX@hxsr2&I4=VbaDxaSaNMZE!#G)r#c^^BODwwFW+o;9CYU5AmyNc8rJb%N7XX(L B3rPR~ delta 108 zcmZ3|Ns9_Hehs|T)-FylL5)1E8hHxQGkg9B!jF& R1S9|jTsGPUmUg<9TmWW|6SM#T diff --git a/libs/common/pytz/zoneinfo/Asia/Yangon b/libs/common/pytz/zoneinfo/Asia/Yangon index a00282de340141690412a40fb70cb6d7631ff401..eef37b42e8a0e7179f8113bea01f4a71d668e8ef 100644 GIT binary patch delta 91 zcmZ3$^pA0ZxCR3fU;(mOfmo;kNG$wR9C-TEABl^z(myaVF;Bd1I6<5jC=LfK6BlbR VLuB~4Y_tu`j1BB`&8&8oT9-4Vj!uO0P!J!%SPM4%-FzA*UZY;fC~VnOBm1q diff --git a/libs/common/pytz/zoneinfo/Asia/Yekaterinburg b/libs/common/pytz/zoneinfo/Asia/Yekaterinburg index fff9f3b14bfebc4344701c7b66410602de37e6ba..d4d19ccf1e91121589a7cabff444bc916f67f9ce 100644 GIT binary patch delta 71 zcmey&d6sj6I4=Vb@PG&)aN4LMz&P20(P?ruV+Ok1W??1?CYU5AmyNc8shzGV7XZE1 B3^D)! delta 110 zcmX@h`I&QqI4=(a0|N+yfb&Kb0Y;Yk|Ns9_mSS<59Lym7dtc%Mzcu9vlMNUZCI>RgFtScAU<{vpkr7Sx f=C6!AjFUN;A94dVfb?T(;^eZ?HZZZ%HQ@pP)rT4h delta 218 zcmey!v7U2+I4>6i0|N+yfa64!4i=-61qUXsDdd+kbMWm;eBigHJfZ&o|NoO!7!?$l zSy^yHpycF4#&DQnK=aUz-+YIWhY@VSqI}v58O8Ng`O-F| zN(+`-s@05gbj2;-*hXbwTUL%wjHo!5sC=zNx7VU$a%xVD%<^W3M|t!4x7iY3YdbwLh#5o78DiED z^M;r?#M~if4>5m;1`r(}T0rz5GKmlc4-f_-4n!bNjsy|PlVd>y^W3v-=qL3K>z>% delta 429 zcmca3bc}z3xFjCX+WVG6XXB?hpiYG{{KIIfPv-z z|Em`mIedIW7#xFv*d+wyW+0d#E{oL{|A7GHlf`8$ECcNoJL0^;1@xH-&}TZn#(GA2 O20FfmhI)p223!CbWif96 diff --git a/libs/common/pytz/zoneinfo/Atlantic/Cape_Verde b/libs/common/pytz/zoneinfo/Atlantic/Cape_Verde index e2a49d248de086306d2dd16a2b2d497a008c8f07..0d0d31a2f092d03f8512ed9c34f36a3f3f21209b 100644 GIT binary patch delta 53 zcmeBUYG9h6EW^UUz`zQ`Jd>WST5x9S9ED3IH3Hn?-yIm4m?vH|omi>N$TD%R1~Zsz F3jmi*56%Dp delta 73 zcmZo*>SLOqtjNm1zyQKLlb)?waAxWpg-azh0^H)?9qRx8|If(8%renFf{}IN3=JNj SA_jz@DF>I0u7RPQAr}Bk4i)$S diff --git a/libs/common/pytz/zoneinfo/Atlantic/Jan_Mayen b/libs/common/pytz/zoneinfo/Atlantic/Jan_Mayen index c6842af88c290ac7676c84846505884bbdcf652f..7f6d958f8630cba512d8e58ca8edfbd516291522 100644 GIT binary patch delta 761 zcmX>k_)BntxG5(C0|N+yKtB+J*g_3JVm6O>!tATJKFqnfZo=FpTPMuhIbp*5mgy5t za28IOXd_{AaoaS8OA-4TE-myIxST9K;flBNgsYYp6|SZ~pKx_WF~c>@Edtkdqy%no zP7}Dvba2AWmlXmVr(9;NXJ&yzRz?t;4NS7IbI=8vqgcbhz|#TrEQ5>!BMSoqp8&}J z$ZRbGMqUO65II?ZSyI5+)i(sBoRJZT7#SEQ8!*d-VhzIoK#;#E@dt}K|53l%|&!G$e D`9jF! delta 681 zcmew*ct~)9xGFmX0|N+yKpzl;*t`uuVz&BYhS^uD6HaXRO_=B-;czL5UEuY4g$Zv~ z_z1k6VJq;iLr>s+g}lIr3;}_U5sU(#+&>9?Hh(PeMg5AvSMj3)-vmBP_^!_;@cqN3 z2}0+lf7m$VGGjd>GYb~T#L7k=sD*3|0|S=`(4P!43XCia4159%oG=n3GFg#X5)>AU zKrq>hSY`TqIW7eAQ}{4U>X=`AQ}{KllL;4)Pq6}BmfFM5Df}J5Df}N5Df}R z5Df}V5Df}Z5Df}d5Df}h5Df}l5Df}p5Df}t5Df}x5Df}#FbxcGkjFrw4x~0muqh*_!b<)8;ZBCdSE3tU$TVYOFd;+#t#SKmbzU M%dX7|R3OU;02r1TF8}}l delta 42 ycmZ24Jz08!GUMHiDiVyFT^NrsZRTN7W@Nm(*??7tX>$&{8tY;n7RJd_c%=aucnjwM diff --git a/libs/common/pytz/zoneinfo/Atlantic/Reykjavik b/libs/common/pytz/zoneinfo/Atlantic/Reykjavik index ac6bd69731d25fc20724798abd4f683f1f2ccbd6..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch literal 148 zcmWHE%1kq2zzZ0GvP?kCG3nVP561uh|5!kkv-tRiFt`J82nmM#2LhZ1aRE&;-~s@2 CSQl9U literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}ZK$jMYysKwL@$Wb#X)mML|W;t;7K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3WN{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT diff --git a/libs/common/pytz/zoneinfo/Atlantic/South_Georgia b/libs/common/pytz/zoneinfo/Atlantic/South_Georgia index b3311b6331471833893fcb776ac88e2a90a607a8..a2b59a9d1088690cb2f9ad9011bfa59e6cb5c658 100644 GIT binary patch literal 150 zcmWHE%1kq2zzZ0GvP?kC(EubE|Np;xfPvxv|Hls)SbTg#7<3Jc7(z%ed^o>T-ejT<*Rh23!D!s0G3R diff --git a/libs/common/pytz/zoneinfo/Atlantic/Stanley b/libs/common/pytz/zoneinfo/Atlantic/Stanley index 2fd42a2c34e3f4cd0e107b818abe108f3be02d4b..1527d0e1a762674748735a95b6c3034d162f4240 100644 GIT binary patch delta 70 wcmcb`xq)+nI4=VdaNDS&z&P2S#cgswqaC{3=KqWqOfX3fE*o6~V>@Fm0JrE2#{d8T delta 113 zcmdnMd5d#`I4?T`0|N+yfcr)j1xA+o|Ns9_c42gz+{tLq2a$l0=;}AWVYFc40Ldb2 R5`!s+&|EgU2F7;ATmZt76YKy0 diff --git a/libs/common/pytz/zoneinfo/Australia/ACT b/libs/common/pytz/zoneinfo/Australia/ACT index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0aea4c3d43e504dafabc031d7ca9cbe8db46163c 100644 GIT binary patch delta 544 zcmZ1`*e5ta+>!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|eaH=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzGJ~5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0EPQvd;kCd delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq4x;T=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzGJ~5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0X~apJOBUy diff --git a/libs/common/pytz/zoneinfo/Australia/Adelaide b/libs/common/pytz/zoneinfo/Australia/Adelaide index 190b0e33fabb8dd6c01f6d939df0f09e288c562a..f5dedca59e2b220f7395c73f60ff26e610373e8b 100644 GIT binary patch delta 517 zcmdlfxIl1%xFrJ+uz(04=mTO9o38;#%<((fF=vk%$LWw=9H)=xbe!29+i`ZeU&pxx zRUGHr4LUXkh%nVNGcpY%v_!L^H6GHm`|+cc?hG zpo-&syHdr*01>8oCPpY^W*G?Rie@PTL*pBu(-|hOU}RxnXq~~piOlx#4PkI}b^#H= zAs}Upj3AO>f_N$$kOE;4=m%mDo9{mm6pM9>faq!=<^m863KcL73>gp&3LP*F3?UE= z3MCK?3Mmi`3M~)~3bD;Im^E4InVF!Fg>@hxKbobJ{W+wRf#C%VDPVZ9K*9(ZJm7!= N1qU>cCf9Q)0sskNYrOyf diff --git a/libs/common/pytz/zoneinfo/Australia/Brisbane b/libs/common/pytz/zoneinfo/Australia/Brisbane index 26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba..7ff9949ffa93e44835ab133998b89e440094f909 100644 GIT binary patch literal 419 zcmWHE%1kq2zzSHPqJlsg#O7-N5_9|xHO$$g&u}_qGsEfQsSRhghc%pC?%r^2K{3Pm zcFhLn0?P&#UKs{e&$A4w?_V*fYp!QdKYxZnlXD`2=K6IE%#2K^kb$AU0%$J7tPPAn zOL`VCa6(8Q-w*~zR~HZw9KyiB$jA^vf>ZwkLDAd!93UFxWH1eMHi!l}9ZUnA528U{ z0MQ_CfM}3cKs3lZAeVu>1foIS0s)ZMz%QKLjx`V Df+TPt literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h%*Zs5P!Y{?28Nb7K<6_|Ucm_TOX~~<&dL1DqRfnp3=_l! zK@w~r0thAmG27%&W+khW9shv<`|+cc?hG zpo-&syHdr(fTzqk>u+o}VKiXmWMYKE$rsrz85t*EVD~X&U}%{GG>2jG3Pu(NhSnJj zoXBh+-w*~zXBQ9=90F3t$Os}CCWs5N11S&&fr&uOKG}s?$!hJA|3Cn85r_u43QPlC z2BJZ(1JNKCf@qK{K{Uvvn-?&jWdyr5gEfh%o`o3-S=q>h%;ahW=@UTLJUN0xK?@j2 ce2_o_2aGE)OuzvH4kB!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|eaH=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzGJ~5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0EPQvd;kCd delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq4x;T=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzGJ~5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0X~apJOBUy diff --git a/libs/common/pytz/zoneinfo/Australia/Currie b/libs/common/pytz/zoneinfo/Australia/Currie index 865801e5e0befe4e6d1b17a10ed15f721c5ac335..3adb8e1bf7c6ec51f1c100538799271d7d7a6e6f 100644 GIT binary patch delta 713 zcmc(cJxc>Y6h-fBQfMP6LK?+Rw9y|RDB=bi1;Hg?r-g;JB39;g3!)ITi&3{SrO|+b zSlOhAAC--@|H6-PPAmm0A$Qq#9?QV6=brap4^#>p3du7u)ytk2t1wyEcI+}`gG^l`PT&(?~*wj1W_Q&Q>BTso|1 z=0cZCgHbu>qHaFLKQ5Q{hQe>F+Hk?aex}}^xv-dMwiq*(6UjMM3S|!dvgSj!CiDie z2Qdh-2r&t<2{8(>+F>vYu?sN_u?#Wo{j~S!<^#$h+F?gM3;Ic@08j&=ie@c0mhW~K M@qcu2l=h5#1NVor>i_@% delta 568 zcmdlcv`lbW^G{PgpnW-AKwrLN7rBw z;SvH;#>faF877FwVhx4=Kv1H`q5z^np#h>nAp)X7p#r9XAp@d8p#!2pAq1j9p#-8q WA+=eDMT2FagUW>(K{Z*NQyu{PBxn5q diff --git a/libs/common/pytz/zoneinfo/Australia/Darwin b/libs/common/pytz/zoneinfo/Australia/Darwin index cf42d1d878b364a3d720997b1511ae71da458b06..74a30879bc6180d588a706451226cb4c95faf79d 100644 GIT binary patch literal 325 zcmWHE%1kq2zzSHPqMSe)#O7-N5_9}ccFfsh#&J4i7su)2IUQ%V$99}u?$>c{K^4dO zc7qOPMkWYkV5nLFRL?MZ1tU;%>kI~@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=XPVa^_XhPk&~8s;@5Hq76p*03PKx#4uk zW`@(pQyb1~4{JEP+`Zx4f?|gA?V1f615B9enHiY|61w8CoPnWB2Iwt@SsNIEe(YJm zzzHFJd_x!(^4e2h7bWuODog&Ki5`8$U^0Fow! AUH||9 delta 563 zcmdlcG)-uNxFstC0|N+yz%(ERvH2Q+#O$jT4RiJ=d^jDlnc?*D@Paej!y3*mw=6if zpqSx&yL7?E1QVuuMkXj^W*G?Ri^Ea|hAtVP7Z`dLFt9K%%-X=n2_r!wKE5Fgj;_HV z!X*TxjFAyUGE5K`#0ZVa4a`zT3LpLh0Vp8AG%zqgG$=qoG$>F&G$>#|G$?R3zhTZ` Q8R*dQp=Rhz7Uz@)0PlfVQvd(} diff --git a/libs/common/pytz/zoneinfo/Australia/LHI b/libs/common/pytz/zoneinfo/Australia/LHI index 8c6c7dd0b7c464bccd98e87483967fa55c833cf2..069a95ad686c1139e2ff2b9ce94dc5ef5bc98c67 100644 GIT binary patch delta 67 ucmcb?w~cRtI4=VdDBh@Yg>iB%Tk+(NZ03_UGNH+CzRI+K87^(at_A@4$Pf$w delta 101 zcmdnScY|+&I4>&$0|N+yK*>gxD~v4l|NsA={DZN0au$;XCqx2BPF~0)fvR}(F{TB~ N93TZabnvsQ0|2=U7SI3y diff --git a/libs/common/pytz/zoneinfo/Australia/Lindeman b/libs/common/pytz/zoneinfo/Australia/Lindeman index 8ee1a6f548b0518bf38c99632a6cdbac5d23dc32..4ee1825abfe65887069dcbd10bcf786d50ba0702 100644 GIT binary patch literal 475 zcmWHE%1kq2zzSHPqM|?=#O7-N5_9|xHO$$g&u}_qGsEfQsSRhghc%pC?%r^2K{3Pm zcFhLn0?P&#UKs{e&$A4w?_V*fYp!QdKYxZnlXD`2=K6IES}%(kv}c@Z(7Dv#pj)w~ zftisB6EZM#uL0V{Flz%N(Bhs244e?s$2Ww*(bWY+1cxv%FfuZPkl_6PKv48{(G?I4 z@&=d&dIdy-yaT3zUINh|Z-Ho#*FZGLdmtL*MUcxt-UQJguYv%`yI>mVWe@;)8%zVe k4gw(WgK1z8fB+~MKmZgJAQ}`LAest6!UYN(T|)yd00dHiMF0Q* literal 513 zcmWHE%1kq2zyPd35fBCeF(3x9`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIi0T~apUKTTG&p6ef zbE&^Uw_;5L6C)Hdvp^sdh+<&qUIVm{Vb%slAiHM)11B=u$2Ww*(bWY+1c!i>F*1Tk zh7c0`^B)L`-Y&WVqCx%w(?Gw0XpsNFG|-PA8stw94e~392Kg66gZvD#7vygc4e~n( zfcy`pfnfjwpl|@wz_0)TPdaC|tlaFl;~o6h0st6hiB%Tk+(NZ03_UGNH+CzRI+K87^(at_A@4$Pf$w delta 101 zcmdnScY|+&I4>&$0|N+yK*>gxD~v4l|NsA={DZN0au$;XCqx2BPF~0)fvR}(F{TB~ N93TZabnvsQ0|2=U7SI3y diff --git a/libs/common/pytz/zoneinfo/Australia/Melbourne b/libs/common/pytz/zoneinfo/Australia/Melbourne index 3f2d3d7f176b9e461f9730117bbf4771528da823..ee903f4b1fc292bc9cbec7b501a266030ef3510e 100644 GIT binary patch delta 544 zcmZ1`*e5ta+>!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|j5z=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzE{U5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0FxDBng9R* delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq5TEW=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzE{U5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0Zw&jS^xk5 diff --git a/libs/common/pytz/zoneinfo/Australia/NSW b/libs/common/pytz/zoneinfo/Australia/NSW index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0aea4c3d43e504dafabc031d7ca9cbe8db46163c 100644 GIT binary patch delta 544 zcmZ1`*e5ta+>!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|eaH=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzGJ~5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0EPQvd;kCd delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq4x;T=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzGJ~5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0X~apJOBUy diff --git a/libs/common/pytz/zoneinfo/Australia/North b/libs/common/pytz/zoneinfo/Australia/North index cf42d1d878b364a3d720997b1511ae71da458b06..74a30879bc6180d588a706451226cb4c95faf79d 100644 GIT binary patch literal 325 zcmWHE%1kq2zzSHPqMSe)#O7-N5_9}ccFfsh#&J4i7su)2IUQ%V$99}u?$>c{K^4dO zc7qOPMkWYkV5nLFRL?MZ1tU;%>kI~@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=%Tj6xbd4;gu z&K#f(40RJ2fz}l?FmOUhAKwrL$8Z-A5gfw6z{toDLV~0J13}3$u_quJ+uWJp!UZo&nJy4}n|)@)Vc`dJIH^JO`$M9t6=KPl9QnM?o~mvmhGeVGs@SG?)f@ U97KaW52h&%04`8a=vr_A0IP9yp8x;= literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ diff --git a/libs/common/pytz/zoneinfo/Australia/Queensland b/libs/common/pytz/zoneinfo/Australia/Queensland index 26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba..7ff9949ffa93e44835ab133998b89e440094f909 100644 GIT binary patch literal 419 zcmWHE%1kq2zzSHPqJlsg#O7-N5_9|xHO$$g&u}_qGsEfQsSRhghc%pC?%r^2K{3Pm zcFhLn0?P&#UKs{e&$A4w?_V*fYp!QdKYxZnlXD`2=K6IE%#2K^kb$AU0%$J7tPPAn zOL`VCa6(8Q-w*~zR~HZw9KyiB$jA^vf>ZwkLDAd!93UFxWH1eMHi!l}9ZUnA528U{ z0MQ_CfM}3cKs3lZAeVu>1foIS0s)ZMz%QKLjx`V Df+TPt literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h^H6GHm`|+cc?hG zpo-&syHdr*01>8oCPpY^W*G?Rie@PTL*pBu(-|hOU}RxnXq~~piOlx#4PkI}b^#H= zAs}Upj3AO>f_N$$kOE;4=m%mDo9{mm6pM9>faq!=<^m863KcL73>gp&3LP*F3?UE= z3MCK?3Mmi`3M~)~3bD;Im^E4InVF!Fg>@hxKbobJ{W+wRf#C%VDPVZ9K*9(ZJm7!= N1qU>cCf9Q)0sskNYrOyf diff --git a/libs/common/pytz/zoneinfo/Australia/Sydney b/libs/common/pytz/zoneinfo/Australia/Sydney index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0aea4c3d43e504dafabc031d7ca9cbe8db46163c 100644 GIT binary patch delta 544 zcmZ1`*e5ta+>!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|eaH=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzGJ~5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0EPQvd;kCd delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq4x;T=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzGJ~5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0X~apJOBUy diff --git a/libs/common/pytz/zoneinfo/Australia/Tasmania b/libs/common/pytz/zoneinfo/Australia/Tasmania index 92d1215d60f929545276892330917df09eb8d1e4..3adb8e1bf7c6ec51f1c100538799271d7d7a6e6f 100644 GIT binary patch delta 618 zcmbOxv`uJ&xF-V;uz(04m<7ZjHeUmfn0>XPVa^_XhPk&~8s;@5Hq76p*03PKx#4uk zW`@(pQyb1~4{JEP+`Zx4f?|gA?V1f615B9enHiY|61w8CoPnWB2Iwt@SsNIEe(YJm zzzHFJd_x!(^4e2h7bWuODog&Ki5`8$U^0Fow! AUH||9 delta 563 zcmdlcG)-uNxFstC0|N+yz%(ERvH2Q+#O$jT4RiJ=d^jDlnc?*D@Paej!y3*mw=6if zpqSx&yL7?E1QVuuMkXj^W*G?Ri^Ea|hAtVP7Z`dLFt9K%%-X=n2_r!wKE5Fgj;_HV z!X*TxjFAyUGE5K`#0ZVa4a`zT3LpLh0Vp8AG%zqgG$=qoG$>F&G$>#|G$?R3zhTZ` Q8R*dQp=Rhz7Uz@)0PlfVQvd(} diff --git a/libs/common/pytz/zoneinfo/Australia/Victoria b/libs/common/pytz/zoneinfo/Australia/Victoria index 3f2d3d7f176b9e461f9730117bbf4771528da823..ee903f4b1fc292bc9cbec7b501a266030ef3510e 100644 GIT binary patch delta 544 zcmZ1`*e5ta+>!wZSU?02^Z_x5&DQ`V=J*|In6pQp;dIDmhSSGW8_sMGYdE{yz2V$~ zVuthWnhhHRM40NC8JPwWTB2Fbz|j5z=yZlz8yJE9=vlzP2_b!aLl_)gT|h)|2m=En zBf|voSdc;tpZ^DfVzE{U5Df|eFbxa^5Df|mFbxb15Df|u5Df|y5Df|$5Df~Q&2yMF QSOz+HEXWO>$?Y8S0FxDBng9R* delta 568 zcmeAZTqZa{+>(`nfdPa;pbv;aY`z8{F~@J&oH=_GKAaBO%y9a6c)^+NVGU=OTNa#K zP|R??UAkanfCy7P6C)HdvkU}uMYEKFq5TEW=?t?rFtRW(^ekZDL}vT=hA=p~x`2q_ z5RfuPMi9v`K|B^~DEtS4VzE{U5Df|qFbxb55Df|yFbxbD5Df|)5Df|;5Df|?5Df~c U%@de4SOz+%T&NLLlk+*`0Zw&jS^xk5 diff --git a/libs/common/pytz/zoneinfo/Australia/West b/libs/common/pytz/zoneinfo/Australia/West index d38b67e2f953dcdfe942ab3a5123f63d24313515..f8ddbdf215d34b022af11c3d1930dd6ea4dca87e 100644 GIT binary patch literal 446 zcmWHE%1kq2zzSHPqQXEL#O7-N5_A0SEts>%Tj6xbd4;gu z&K#f(40RJ2fz}l?FmOUhAKwrL$8Z-A5gfw6z{toDLV~0J13}3$u_quJ+uWJp!UZo&nJy4}n|)@)Vc`dJIH^JO`$M9t6=KPl9QnM?o~mvmhGeVGs@SG?)f@ U97KaW52h&%04`8a=vr_A0IP9yp8x;= literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ diff --git a/libs/common/pytz/zoneinfo/Australia/Yancowinna b/libs/common/pytz/zoneinfo/Australia/Yancowinna index 874c86505c896406516a16dd21b9ddd8d0ba2d95..698c76e30e91f568a29daca12993cfacbfdbf83e 100644 GIT binary patch delta 414 zcmcaBxK(h1xFrJ+uz(04=mTO9o38;#%<((fF=vk%$LWw=9H)=xbe!29+i`ZeU&pxx zRUGHr4LT+UJl$-`sLxo>%*Zs5P!Y{?28Nb7K<6_|Ucm_TOX~~<&dL1DqRfnp3=_l! zK@w~r0thAmG27%&W+khW9shv<`|+cc?hG zpo-&syHdr(fTzqk>u+o}VKiXmWMYKE$rsrz85t*EVD~X&U}%{GG>2jG3Pu(NhSnJj zoXBh+-w*~zXBQ9=90F3t$Os}CCWs5N11S&&fr&uOKG}s?$!hJA|3Cn85r_u43QPlC z2BJZ(1JNKCf@qK{K{Uvvn-?&jWdyr5gEfh%o`o3-S=q>h%;ahW=@UTLJUN0xK?@j2 ce2_o_2aGE)OuzvH4kBfdPa;Kw+Xv3rqd~|Nkd$5S_T6ZL$Fq%fw^aoDfMMIq`!8s`|yE XjEo#0X$(z5a7_$cHo69;cBWha!o(PR diff --git a/libs/common/pytz/zoneinfo/Brazil/DeNoronha b/libs/common/pytz/zoneinfo/Brazil/DeNoronha index 95ff8a2573f4dbb03892a576c198573895a01985..73b4b336ab55544e6061409261c16cacc39aff61 100644 GIT binary patch delta 75 zcmcb?x{q~&I4=VdP@kx>Wa0&7W=1B)$*Y;<(PcN=Fgh?I$#Zbo=o%Q=8F2vs`AQ88 delta 94 zcmdnTdV_U>I4?5;0|N+yfW}0XB`o#-|Noy{$fQ2;oje;v7+uw7Lq-Qi4v-v@T0V$c ME*o6~BReB50L!Km;Q#;t diff --git a/libs/common/pytz/zoneinfo/Brazil/East b/libs/common/pytz/zoneinfo/Brazil/East index c417ba1da757e94b88919b05df8a21b35a5bf66d..67935ff4da8f527e03cd05ed2aa20e601e10f921 100644 GIT binary patch delta 70 wcmcb_KaG2WI4=Vdh~B7D!Z_K0DSC1ryBxaQ<}XYSm|&6|TsFD}#&*VB0L|EQ13aJSs=yWeo%wAbEPt0)R?T)B!hRin>T zM~vBtV>WkWHRiP{U)UZde+%b#{mr?IzqIB=n2sj=6d4{Zsk6Vzs4>!w&A6sn1}kv Y<#CCp(_{B)nnQC?KiR!bhYJVuFV&%*3;+NC diff --git a/libs/common/pytz/zoneinfo/Brazil/West b/libs/common/pytz/zoneinfo/Brazil/West index b10241e68dd415354ef12cc18fb1e61df3558104..2708baea5af1d5169a1cebd00c4762d6bb937fda 100644 GIT binary patch delta 72 wcmaFCa*kz!I4=Vdke{g1GI4`6Gb0n@#MAQVl8Z$c84xQ`~Uy| delta 145 zcmZn@`X)F*T#$ukwnc1$GUKj|Djyg(OE6V2PF~1tG}(ZeZ}L4ZpULtpK&j2nEO(eUt8jc1CQ1CzB(`MsFJyAho%Zjc1CQ1CsT0kMsFJyAho%ZFGDP~{(ngvQ>GqA6a) zH6&V^LfVbSrf6yj_m;Bvxv8NJ?tU-a4flVZx~jV9^K>{I4sI?>-JHF*zKmuS^p)_d zTs^%IYvXmfp4b-~-4z)NE{a&QBoo6kBGHJ;Wc#2<-h|}ldrNFx#*FRf0evSQG*Y#1 zJ-sk$?4G^pdsA*BlYZ2*o~DtTtLnK{r_4{+MgH|e9`skl!9zn9K1<^8x+afa;^O$^ zTo&E;A#t*tmDbgmC`)arT-S!mUz)$%)8^IrTaAah@V_Bwec?dB448h`4?Y!~wEzGB diff --git a/libs/common/pytz/zoneinfo/Chile/Continental b/libs/common/pytz/zoneinfo/Chile/Continental index 816a0428188d99f437004312ee73c3860ee0f54f..010c6bd04cae79078540da560ce38400bfe0ade6 100644 GIT binary patch delta 218 zcmaDTd|7ycvf_LO1_llw7HI$y{RZx?vtynH%&&QLbRk3GfemrxM<=?NGK*g|*jTfX zky$p@VX^^Jg$N@PBQqll6AUslF|)EvKETw?_5c6g-3$yMa&j1Rt|i>?|3Hx0E_)P2 sgDe5jAd5gW$TAQOvJgarES>y^ITvK><{Xw(X0Wwa*gp_uZ5d}a0H5MkAOHXW delta 248 zcmcaC{7`s;vf=^;1_llw7HI$y{RZxCvtynH%&&QLY#~G8femrx$0oX%GE1M_u(4(% zBeRIofyo9;6;k#8|Nm!XVq|7yVS+(sCT3P3p8SxbcJc=f9&Wb(|M%`EYh2ckiCf@qMvAR1&hhz8j|`8#ti$N`(vSyGw7E;!HrK@Mmd QLp@LhNv`mkyopm00GOC#8UO$Q diff --git a/libs/common/pytz/zoneinfo/Chile/EasterIsland b/libs/common/pytz/zoneinfo/Chile/EasterIsland index cae3744096402e8a452336544edf96ca9ae5ad8d..184cb6a83b3392d0492c42297531c85e7e38c4f5 100644 GIT binary patch delta 55 zcmdlfxLR<6GGq5fm7R>tvat@6A23!-KEU2R*@wetvI#R#W^)8{E;Cr_1ltFwR4xD) CWD`^X delta 81 zcmZ22xKnU~GGot1m7R>tB1#7)KVYn2tN;K1KO@WJ1SZkR`Aj8~Z8>}=>oWrtZ4PG6 SWd^G{%=Uqc0SGWu<^cejdK>Qm diff --git a/libs/common/pytz/zoneinfo/Cuba b/libs/common/pytz/zoneinfo/Cuba index 8186060a4b2a81934eff93a2733b581bf60f299a..b69ac4510784f23ee794cd6d11e62315a7318e5e 100644 GIT binary patch delta 29 jcmew(^g(EXI3vU43CtoJb=;Ui?9CIHPq9u;;FJLXk%tKG delta 63 zcmew$^haodI3wFei6~|^1~33|H+M0gVpW3hxtxPTOr2dqbbO8VjPwlj4Rm}B4fPCx HEJH2;v%d_P diff --git a/libs/common/pytz/zoneinfo/EET b/libs/common/pytz/zoneinfo/EET index d2f54c9bae1f689433b7da8bc38655c7f2aad22d..cbdb71ddd38be8f4a23e57bdb4b86e52195e9f89 100644 GIT binary patch delta 127 zcmcb@_l0kQxF8Dy0|N+yKotpSd4%Pqw delta 95 zcmeyucZF|)xF8b)0|N+yKot-(ZPdtS5&<%_3>aBlT|*dLU4ugy7=Zwr;?4J&f>{L6 KRZk9LR{{WlQw^8^ diff --git a/libs/common/pytz/zoneinfo/EST b/libs/common/pytz/zoneinfo/EST index 074a4fc76ad816447121db6cd004aa83ea41d437..21ebc00b3fc096035b9810519d778d04a3562a44 100644 GIT binary patch literal 114 lcmWHE%1kq2AP5+NDp>yi-?@Q-!8JI9A%rYlTtKa+TmXY14MYF{ delta 36 icmXRan;_20$iTqBI8jE5iGcx$$HBnBpfrgKIDl chXZjy2uK$rgurIj=3HhsR&orRe2+r{0CYtx`~Uy| delta 145 zcmZn@`X)F*T#$uky+nF~xF9D30|N+yz!M3F(s3E@+D^9$qhU#^&D*M0|~jImjD0%i+}P; v7M01DSu~g!87Kc|QIy4SJJ9_i|A8RswyY_eSF#>t8|*%r!>a}WpdKwH delta 428 zcmZ1?eMowOxF8n;0|N+yz*8XRny6vHvUG|)!^VV6W=6Kj7nwaLKVasl=Va#?NXQMf z{Qv)7Dw7R(RhSqACI|59@G&qlf}IW|fUF7PvKamW%82|2g7Ax6cP1OKrhq-Sc>?QE Qwn6VZ7LcDN*YTyi|M-D{LD#^LA%rYlTsA|x|$iTqBI8jE5iGcx$$Hlz}LTsFFf26l!9TmWXE1zP|B delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut3oU}tE+1psy~1!DjJ diff --git a/libs/common/pytz/zoneinfo/Etc/GMT+11 b/libs/common/pytz/zoneinfo/Etc/GMT+11 index 72a912e050e1af97d12a541f69ee01584c52f509..d969982309e5ca7d32979a7dad814ca307d2cd8d 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsFFfhIWRATmWXL1zi9D delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut3oXlH211psz61!VvL diff --git a/libs/common/pytz/zoneinfo/Etc/GMT+12 b/libs/common/pytz/zoneinfo/Etc/GMT+12 index 6938a1aff2b9a786015ebc0d0ea4d95bcf146f64..cdeec90973be28ee4075eadd22b8b574db2d7a5f 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsFFfMs|iqTmWXS1z!LF delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut3oWM^o^1pszD1!n*N diff --git a/libs/common/pytz/zoneinfo/Etc/GMT+2 b/libs/common/pytz/zoneinfo/Etc/GMT+2 index a3155777077f680d885a3e820fe0a84d5b238d58..fbd2a941fda996f4abc1f0e09cdf99c271f5a1e2 100644 GIT binary patch literal 116 ncmWHE%1kq2AP5+NDp>yifBb-fLD#^DA%rYlTsA|x|$iTqBI8jE5iGcx$$Hlyizj}dzLD#^TA%rYlTsA|x|$iTqBI8jE5iGcx$$HlyiKYoCLLD#^9A%rYlTsA|x|$iTqBI8jE5iGcx$$Hlyi-?@Q-LD#^PA%rYlTsA|x|$iTqBI8jE5iGcx$$HlyiU%h~VLD#^HA%rYlTsA|x|$iTqBI8jE5iGcx$$HlyipF4qpLD#^XA%rYlTsA|x|$iTqBI8jE5iGcx$$Hlyi?{8pW&^54N2q8-smkm&_odp*FWlRhR delta 38 kcmXS^m>|x|$iTqBI8jE5iGcx$$HlyiZ!BP7&^54R2q8-smkm&_oh26lTc``r delta 38 kcmXS^m>|x|$iTqBI8jE5iGcx$$Hlz}LTsGPUhIYD!TmWW=1y%q6 delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iXs2t)1psyx1zrFE diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-10 b/libs/common/pytz/zoneinfo/Etc/GMT-10 index 352ec08a14a107fbcff0844659e60e8bf3952a92..68ff77db0d95c7d054ef33c05e05ba71bcbbbdd8 100644 GIT binary patch delta 35 ecmb;_n;_2000I+bq>z|gTsGQ<26noJ23!DagawHJ delta 40 mcmXS`njp@~$iTqBI8jE5iGcx$$HTzDWut9qV5e(nzy$z&CIyiI diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-11 b/libs/common/pytz/zoneinfo/Etc/GMT-11 index dfa27fec76820a74da31e40ff8698fcfea3fbc8b..66af5a42be440f1fb8fec3b915afb49b356f63a5 100644 GIT binary patch delta 35 ecmb;_n;_2000I+bq>z|gTsGQz|gTsGQz|gTsGQ<#&)`f##{hwoCS{n delta 40 mcmXS`njp@~$iTqBI8jE5iGcx$$HTzDWut9qY^Q5z%mn~^J_VNm diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-14 b/libs/common/pytz/zoneinfo/Etc/GMT-14 index 35add05a605a4ed9d7f353c78ee3db8b014e8141..7e9f9c465ce6211c65d617f60472c9b55b5052c5 100644 GIT binary patch delta 35 ecmb;_n;_2000I+bq>z|gTsGQ=x delta 40 mcmXS`njp@~$iTqBI8jE5iGcx$$HTzDWut9qVyA0p!UX_)Mg^Gw diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-2 b/libs/common/pytz/zoneinfo/Etc/GMT-2 index 315cae4f9e535ee35b868a0e3709e2a7fab6a606..fcef6d9acb247deb539fcc4b30149802572ea642 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPUMs~VJTmWW{1y}$8 delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iWT$Jy1psy&1z-RG diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-3 b/libs/common/pytz/zoneinfo/Etc/GMT-3 index 7489a153dbb61b904090d6bb484c6f29770f44a1..27973bc857b4e618218ca2790acacb81f7c7bb82 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPU#&){KTmWX31zG?A delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iY^Q6?1psy<1!4dI diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-4 b/libs/common/pytz/zoneinfo/Etc/GMT-4 index 560243e841ff12e0554eb328e40dbcbdd65b1327..1efd841261a977ae218d408f9cc308c3e312a5e8 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPUCU&|eTmWXA1zZ3C delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iVyA1u1psy`1!MpK diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-5 b/libs/common/pytz/zoneinfo/Etc/GMT-5 index b2bbe977df886770874563aaf86d7a358814ac00..1f761844fc44f8228bb748235bfd30be6c389cd1 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPUrgplfTmWXH1zrFE delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iYNu<;1psz21!e#M diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-6 b/libs/common/pytz/zoneinfo/Etc/GMT-6 index b979dbbc5c86789f34638aebc9644e7af0fb83bc..952681ed46cb60e59baf76a2c43b49d5f67255d1 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPUW_G$}TmWXO1z-RG delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iW~Xb$1psz91!w>O diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-7 b/libs/common/pytz/zoneinfo/Etc/GMT-7 index 365ab1f64683d2b1867072378c1adcdf289e432a..cefc9126c691060225ff2eee1241b1e5e9825fcd 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPU=61T~TmWXV1!4dI delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iZl`O`1pszG1!@2Q diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-8 b/libs/common/pytz/zoneinfo/Etc/GMT-8 index 742082fcd4fcf8bc9c1a8b8f35af7533d74b0a3a..afb093da00685297cb11347c4840acf3a8e2e2bf 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPU7IwN8TmWXc1!MpK delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iVW(@s1pszN1#AES diff --git a/libs/common/pytz/zoneinfo/Etc/GMT-9 b/libs/common/pytz/zoneinfo/Etc/GMT-9 index abc0b2758a3b5fffe3acff6d42973ce9632acc33..9265fb7c2071ec0e66c657ad2ae42d5dd525fe97 100644 GIT binary patch delta 34 dcmb;{ogmK200I+bq>z}LTsGPUmUg<9TmWXj1!e#M delta 39 lcmXS|oFLB1$iTqBI8jE5iGcx$$IZaNWut9iX{T$+1pszU1#SQU diff --git a/libs/common/pytz/zoneinfo/Etc/GMT0 b/libs/common/pytz/zoneinfo/Etc/GMT0 index 2ee14295f108ab15ee013cd912e7688407fa3cde..c63474664a289aa3c3c0d8b2ce06d484679754c0 100644 GIT binary patch literal 114 hcmWHE%1kq2AP5+NDp(+@+u$qQ{X4_S*>cX-9P;=H2 zb@51vYRNsWS}TL0OPRTw^ z9r`HmMaSg*raJpUV~>0oSs@1wHONPMPs+y?hvkzEYwV}%cFSk$2jpPIggrQ!Xb%-F zmoKJ@&6n+I)^Kya`RYuLIdY)V8cnSZn6J0xS!1X3%<Q>+AyT^3VnXDE2nvxDA}T~yh_JA;*5{t_mfv@f zmoE?)A~8f{h|Cb7AyT{4VngJH2o8}PB05BNi0}~UA>waggZ+>MKvDon0wfKPL_kvE zQYQnFj`}2jW>6=B^QaC5Bp8rzKsO*o{t=H3h{x~o`nJQvge literal 2940 zcmeIzdrVe!9LMnog)}b zW0_O)hM*1+4Q~Uy-n?{>i0gaE!RJ_{^+01;rw3DbNCnDAK!#o zd2yb~`mMe=*Rn#S?>i7(^^@lvCx^lGH z*0{pdywTTe-`>saD0s~IVA(jQHlvTTbM9iZ%j-D1eS4if?VFvw!%NKGAI~^-VRKB~ z*{i1h_C}{+Povp){;b*mUX3}>kYf&(EHj5HPnts)a=~V)0;!JaecCUv}}N!c`Z^tE)0=R(uZriJ}m|9%BN}2+nuHTq7;>) z1nrO;rNO>1?U*(~?v4LhJ4Jl2AssJhXzSA&dVQaUUEZemeY#dVAAU!>)Hg_YRkd_o zwMM!bmG1Ljl^*$p(ldLS+@I=`h@mO+Ky-pchDXVRPMAamBx}@-0ov=UNbTJeqJ56G z(Z02}G`jp-?N@eQV-}s%{zdib&E2Z8Ip-vtw*VcV%GL)shfbA_*;}GN?XbyUQcGOGD&8QuDzjBY$3k6+#(W2(1E>Z$#jw$w;^)jEA*_H4;8uj|;S-qLaT z({=nq`RYp@rB8;YYi4wB&AjQ=tWF~(>+@iJ`bICAu9V&%+p)xnNn-rzomKQsCq_}@4ExvY1N`h``$(bWE zulbz5R8t`@H`eL=6^C>|^=f@(Ub&VoE!9?jty=s4&AUy&UG@%ad$)VHyT|#jZjZ;m z%>FNWg3JB<-Op|B{On)jaqOD|e!+j7LE;JXc*1-5h2MGp{+=^6&F4uU@AD*C`+B`` zUVFycGdaa)ANvb?`Ps`J{QT*%-+cd|BEQksUuQIq>@M} zky`SXKrvlyHIZ^6^+XDaR1_&GQd6X;NL7)tB6W4Og+(fhloqKiQe33ENO_U^A_Yb& zjFcFuF;Zlt%1D`!IwOU4wUtInjno<`Hd1Y*+(^BVf+H12N{-YVDLPVhr0huDk;1#$ z$|I$BwY5izk5nHiKT>~W0gx3ymH=4;WD$^6K$Zbn2V^0Tl|Yul)vg7y7_N3TkmW$u z16dGcMUW*y)&yA;WL1!5LDmIX7-VITrE#@ugDj4#T^(e3ko7?p2w5RyiI6oy771A; zWSNk4LKX^HDP*aTwL%ul)vgw@T*!JM3x=#1vSi4bA&Z8r8nSH2x*-dPtQ@j*$l4)` z=W16ESw2_0e#inMD~K#1vWCbaBCCl1FQX^OT};k@wTyvwKl{ZppwVgZF#}_~(P^<> I>>nTa8?hLa1^@s6 diff --git a/libs/common/pytz/zoneinfo/Europe/Astrakhan b/libs/common/pytz/zoneinfo/Europe/Astrakhan index 5e069ea5b3a9c8eaaeb9c48abe3d9a4b89ee3b1a..a41624f5df9698d78049008a2bd8a77395c0480a 100644 GIT binary patch delta 63 zcmbQw`JZEg_~bi`9IOmL;IL7JgK@F}qr>DvMt@}S&2Jewm|zl|TsGPUCU&|eTmTTX B4>$k- delta 104 zcmey*F`sjSI4>sy0|N+yfa69L4n~&x|Ns9_)?sv*oXr>jlVM;$SGxH=BL@=)NEfnt SA&>wVaM@@ZnAqu>Z~*`=1QT=s diff --git a/libs/common/pytz/zoneinfo/Europe/Belfast b/libs/common/pytz/zoneinfo/Europe/Belfast index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/Europe/Belgrade b/libs/common/pytz/zoneinfo/Europe/Belgrade index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..27de456f16ab549627b284a39e2265cbdb4ad8e9 100644 GIT binary patch delta 74 zcmbQk-@rdXnX!DLiVDZYg*^h7{2V51jM&e}#LP7LB3ssEb9T?k=a_&BCVygb;Q^`m Q4+J1pn}e9knGwoT0TKorWdHyG delta 90 zcmZqRpTj>vnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CFR5;GUk@a iOe_(blUQ~$bF;FrvH?|s;A8`ig2@gXY?BvoR005p#uQ2b delta 93 zcmew>I8$hXGULRFDh@0c7i~_Mn4-fX@HVAjudeE5=Ea obC_Ge3Mao|jsdIO?8362nU{%`g_RAc8U&dq3viT7ZsMp00C$ZV;Q#;t diff --git a/libs/common/pytz/zoneinfo/Europe/Brussels b/libs/common/pytz/zoneinfo/Europe/Brussels index d0d0a08a29c0743517ec537c18ef3111ece34ca5..40d7124e5346af056c75e2f7012a51d94e8154b7 100644 GIT binary patch delta 89 zcmbOz{#9&(GUJYkDsP4J;wD@y5)-)O=P=>&Lo6)Zv9d4&;p7D7yvYU3 sJd@8cPh{LVIe|r#`Nau_%@r(187I54X>2ZKV`X6lA{N%oja&kZ0Dur1s{jB1 delta 116 zcmew=Hc@kn^j%A#@gT-L;2bQNyP`Tp(=2#$) delta 101 zcmeAWTp~C@nXzl4%0~J6Ef3VcawTXy`JJG7ii&JxEIXcSV1e zK*Av?kZKVsD{Cksj;wSv9RutA9D>&Fare2q?fZMSX06OUm5?N( zcZ)e%7~f_0etRSyagMxd`;}s_8hVTFk@vwm`@_(54L35TL1t6 delta 560 zcmbV}ze@sP9EacY4#LPdEz>|qF^4E9P^zh=C5fP*A&BCZmZlpT+LCH&dh3QV%S@sl zOA%sA5N1=5X=g#1gJr1@#g&^&y&r@AfZi{6&)xT?=ed|EYog8xKOv-sT?QY!1?ntP z;4Y(WJRH)^ohS` z@{NBsr!JjJ>ET^^>s7^NpLHPf-bo(|p6=t5aqVYs3jjE*d z8K(_H-K5c7p=ReBZEDC;3!k8t@)@<}k7#o`M33*IoI}T91fC6h0Dh;s#wX=mH>o6b zW~GI<*Vcu_+TGY4@jAP*t{hzKA;fQSJi2#6>k!c@uPuzy1k2nUfsgaQ!@M6fDZG)4nK UIV?~wQWBz9uw5oRoqqf+lZZ;+p)4X&Snc&3l-qGH#A!fdPa;pl_neS>}Wn2^&8wWSktx5j;78gKP3zrfGZ-X&5=Vky#Q| X-{$4aQyD=zH=DBxGUL-~!6^p-VR0Cw diff --git a/libs/common/pytz/zoneinfo/Europe/Copenhagen b/libs/common/pytz/zoneinfo/Europe/Copenhagen index cb2ec0671a372099fbbb7fe8f049ef0fd70889ef..7f6d958f8630cba512d8e58ca8edfbd516291522 100644 GIT binary patch delta 837 zcmchVJ4*vW6h>#Vt4Sov_#&bffmBKi8^6mhomT>}hz}GC zD?xmKjbLY+#8u-T2qK83q|y705Fgl@yF2&XnP!>qY*p3TDVX#NA+S&zyfAlUxfKGz zRKb0G-L4$d&fz)jUJ~uCZqbc@kv2zq62*(WD9v4EOZ!u*obIFBi7>gt#gLn+l6$ly z?gq}(eWX`C`17jbxu(j~iaJ>?OUE$lPm{#lth?#;HRQ?s_>2&Nb-t|V4@pyqHii3t zp9d}3D#U01I-j&>#OU}m8W!)kq~up(;*yervwvp$&b4qb{{|vsP0kra)*$i*kvWLm zL1dpx8wm&D;{XCQSpo=+c#W{{4nKpZ qFUFeTc%-Lp(li?G()sRn>V8Z3fR)H(h zYbRWdTPtw&{sV#Q?k6W)-+xFz=-l)V8)sZ*tY>6qL4r)IZ1janAnO3?f26zJi48 HH4YU3jPQP< diff --git a/libs/common/pytz/zoneinfo/Europe/Dublin b/libs/common/pytz/zoneinfo/Europe/Dublin index 5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1..c729def42fc0822e6b24f7bf503c67cc7e6965f0 100644 GIT binary patch delta 396 zcmX>ky+nF~xF9D30|N+yz!M3F(s3E@+D^9$qhU#^&D*M0|~jImjD0%i+}P; v7M01DSu~g!87Kc|QIy4SJJ9_i|A8RswyY_eSF#>t8|*%r!>a}WpdKwH delta 428 zcmZ1?eMowOxF8n;0|N+yz*8XRny6vHvUG|)!^VV6W=6Kj7nwaLKVasl=Va#?NXQMf z{Qv)7Dw7R(RhSqACI|59@G&qlf}IW|fUF7PvKamW%82|2g7Ax6cP1OKrhq-Sc>?QE Qwn6VZ7LcDN*YT3;+NB^#|l2 xJ^@A+28PK!+&n;vfn)MCZbNPcMn;en1CYk%{LL*aCTxS){VX8&Pd>@53;^rsCF=kH delta 349 zcmew({ziO)xF94zO%*ZtPAXi8|3o9Fa;s5`?{(v09C&0+Uz%W^Y fg=exBi#`)0lP$*l|kfy*9) diff --git a/libs/common/pytz/zoneinfo/Europe/Guernsey b/libs/common/pytz/zoneinfo/Europe/Guernsey index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/Europe/Isle_of_Man b/libs/common/pytz/zoneinfo/Europe/Isle_of_Man index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/Europe/Istanbul b/libs/common/pytz/zoneinfo/Europe/Istanbul index 833d4eba3985c0221daf6789c9ac30266bccacd8..7c2336dd80c3c9cbf71cb53d2b2c1f89a65a8ba5 100644 GIT binary patch delta 532 zcmcJLPbh<79LJyMeP8=ym^ISN{3&x`hb_un*bX~PR!^Cm^v1j}N4=@BVvc%QZoFz- zT$wDNKQhv0-`q>Kcz-?33OAPVa5Bo?DyK$ew#2IQHsifJY}pnBw~uUd`_~e2>dV|| za_3Hni!eP9Pn1zA zqDqLWBBl7nrs8OEIR6|?oGIF|9LL|xo1NLM)!od^RxJfJArDzeXotwdB3cU%+@ptg5HzGvBnTm9pb$M+ z?LGsc4s!_EQ$%~xOXEfShdlpW!WHJ?DJ+%j(qVev0Z-liJ>GnV-{ryQGm|(>zH8E6 z<(s?f?4NukSdy!Kr+jVGVe9Lk`NqK#+Zy}f=U=qgzlwZsdw`X9CEqW-mB+sq_{rd+ zJRLUqpN39J+(lm-DM{V==+j5@VT^O+d6x{8kV_c@8B=iS)eegDFdCF99W z9{=9VDn51b%K1D?ysqQP$#GUSH0NE;-XqmL6JDxm-?`E{;MLG?PW4smftQ}1W$E&) zSHHX?uYE|df>ty#b9a1E)RTxzldezNj`tiw;w)PHn?~12ZjVSBNSb@I0sEnyM16UJNv_1}9 zSU{O@X_hKy;)tefiR{CP(v@l!2 ObfehX%E395Yy1bA+v^el diff --git a/libs/common/pytz/zoneinfo/Europe/Jersey b/libs/common/pytz/zoneinfo/Europe/Jersey index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/Europe/Kaliningrad b/libs/common/pytz/zoneinfo/Europe/Kaliningrad index 982d82a3ac959624e4cd5be0b33f40ed03a46e46..cc99beabe4ffc5107c4719d1201d6583b9ead03a 100644 GIT binary patch delta 339 zcmaFLeU*EHI4=Vb@Ph~-2$-m1BYd$)OyFWo`Gbp7TM{mHtqr&wxou-U8zUq0R1xovVOQ;9OjasPn;udITS<#9lM!@`5)A^`EZxO P+(skUashp#Ys3Wr;Ok0+ delta 360 zcmcc0{giuxI4?f~0|N+yfd51l8~zKw7z-{=ZArM)wKm|=ZMBWXY>bl`SySs-7#L&} z7&sXiEF2hl85pz-7zAM?OoUMstVRkfE(c-z_=Yezy9R@Z5C-317a;a#a0Lmt0tK`U zj6oJNGD1j3AONwDKuA<(nh}sck%J%At&_8vN~A%K{SO2n2ZLykqrvoTwarY-|G+M< O8z7f+0X?W|#03EGmqRK5 diff --git a/libs/common/pytz/zoneinfo/Europe/Kiev b/libs/common/pytz/zoneinfo/Europe/Kiev index 9337c9ea27c0a61b1082f4be37cfb0f9484cf5e2..52efea88065b220e44fd876de3bf3090fe62cc79 100644 GIT binary patch delta 430 zcmZ1>a6({$xFA0R0|N+yKqV0KPt>SXzRJ;{b>wG*_Lf%-I!kUh=uSD)px3guLBC{O zgF(vt2E*9X3>%MRFvfE8z#uOl)#2m@b{8=j1qKcV1}y_dSq26Kd-6GUeHKPW28PLO z%qo~(m~74b(HZCmke@*Q0MQ`7fM}3^Ks3ltAR6Q^5DoGhhz9u&M1%aeIiF<<(;)EV HUv_N(G=W0U delta 396 zcmX>hutH#hxF9bB0|N+yKqV0KPSmJWzVaqP>&W>4?JegLbe3!k(4Dd&L9bynbCfu3Jc?89Y*`fIgG`O_KP1dGC_F(oLn~A2F7-}##{gzk_%q| delta 52 xcmey&(a1SLnbBdR3JW7k{r~^}C*NkapDf7|IC(oGPFoQFdlSn1BPShe&GzO*dT{+hp zxwn(mT)M0nnscl_(EcFTkGZlsZubvsja;o(HrCvCtLDtH`aHkw=9+)C^=F^&zTe09 zjxqk>^^9%U(Oz!daAufqxH-M%=6#NB9;X5);%5c}o4*(H?}~r)+w;f0_JZ?Q?fXV1?cm^qy>K95 zm+U`hm+l$1L-9B5veqxH@~#o7s2P<->t2(}{A04Xyhj#K^+{FkfL2}HB1^Az>#}oU zxqqTXtB2aOW^|c8aA1WleM#E! zt1>>8RmltT&=2oO!|*9-Onj)(gZ;Al^jrFHXQ!;`e@WLq@`|qO*s4uSIyBZ;uj~D* zwK+Ino3DhlC8thWzPeo>{k>Q=oc&EVj{9WO@tM*(@{2sy_p7uGBxQ5kxNO-ssgEyz zU!G`9XuRNvY^@pAcJCS8man?)=ht=nRG03!(5p{gjOkM+H)_YHyX5JYYqWEyNuJqW zpgRv#%C5E5($(&l-DR_7PvojRJJXWx(j4t}zL%ctE83GfFTIz)*5}?ElIN47y7$N@ zx^MWHzR)|UeFyv0b5q96nOT_`xBPv1Z@qr`rcF;@w`Kq5mu2N0HUD2cGtINm@>7S!0t9(U{d>?noqNMVlI|urVBI;=@YwrFr3a zJnro=$Nijscs#LxF_AybX+HZ|9GMC-7i2QXY>??7^Kms3;;%9zWJ<`KT+O7ASs~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!byW|GJ(k!d3HL?(*N6qzbAS7frtY?0}5 zm@hJ6WX8ypkvY4XNh7m%HPc4sjZ7SwIWl!*?#Sek*(1|O=8q%*$pDf9BnL{!yGJ>Q8$qAAaBrC2aEl6HmO=6JDAgMuegCqyZ z4w4=uKS+X*3?V5(a)cxa$r6$#Bu}m;QAnncR3W)Sl7(aoNf(kYBwH5By+ANbx7`z^Ng)28L65274H@*Ael9Ghm%(U7No7f>v?3TS K3%PHLe18GfyY{F6 literal 0 HcmV?d00001 diff --git a/libs/common/pytz/zoneinfo/Europe/Lisbon b/libs/common/pytz/zoneinfo/Europe/Lisbon index 355817b52b1b05680bbb57e4dc8de358eff27a39..55f01930ba92ff6852ae4745e78adb5f96c5b057 100644 GIT binary patch delta 62 zcmeB`UMW36neo9!6$wW6TQ|!UZlBE9Y|VI@X>ujc&&hvTfKrvnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CFDh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/Europe/Luxembourg b/libs/common/pytz/zoneinfo/Europe/Luxembourg index 6c194a5cdcb22da9319183df65478ec4e55555fc..40d7124e5346af056c75e2f7012a51d94e8154b7 100644 GIT binary patch delta 1130 zcmbu7&rcIU6vt<}KUxZb)r2+>4Kj9$D6Co&fkZp-^_X-v=qf%o>!XJ+=z9(Fz=_$MHORykt~ zg?LAWd7z($mu=VkvlU@8`|YQE^vf^PtRM7&kxe#OJBg)y3s1O2V0G=ogq;; z&v&`SJ(@>R71gJEkDcf*W|{w#X`T23jH$Ksmk}ysl?L-McCL0AT|NBV3XdAj|+^2M9wP$8sEKf(@twLKhIqfY1hnIw16MNeY3`2!u)?bONCi z2(3V<1uhO~?iz+URkQ)k@B*r3(%Q18qb5_WUrM3>}_S~ieQ0p4if9?85FUaO62`@Fp UNx;Trb0ijtHYS^*;N@oj0Td%m5C8xG delta 1093 zcmeH_ODIH97{||@J2RRYqmwj~LdvTs-GnWTtUQZMHnNrCW@%w4g|ax@g&2=9hB4l+ z1!bwEydNbi8(t`g}{7SjZ{S zqj^hgF<;VS2@^c_QmigLcCh8!u)1X zxBb*L#pwyS98QOmw#ZgXqWkZAApT3nYzMgFv`ooLm&}>td!EE4H`ZD5XN=F#PMV>w zX!R?8oKRiU$SNuu5E_{uiJuZ`l7vB#9+!DuJZOqF_2+^c)KGK~g*QpX2O$6m2|$Pd zLI#s01PCcWhyg+l5Q2b^#4rMe0aGvmTR<2C!Wt0fm?V2Z7zDy15GH}J34~D~tO8*c sxH;5ru0t@qLo!42+jcBIMRVJ8Wim@EwdcsNsmXpW|J(E{oBXbfcR(sSdH?_b diff --git a/libs/common/pytz/zoneinfo/Europe/Madrid b/libs/common/pytz/zoneinfo/Europe/Madrid index ccc9d85750eaddf9b5eafac627bace09f4c72043..53f4cd101c18058a1ee4d4b9d1184ada11d74232 100644 GIT binary patch delta 81 zcmX>ivQ1=yGUK9&DmyklU}2i<$XPNuk~vw3k%55`2pE6>LNJ0`$x2*G09F(cv;Y7A delta 87 zcmdlcaztc;GUMWjDmz#N-li06yu-r8$Tr!K^STfN0|O%v04WGyWCSVP{FXU~5v+7` TDC-?&h*}&x*%+B7Uu1eaS%FIi0B@WKLI3~& delta 24 gcmdlZvPWdY1tv!3%@>&x*%+B8Uu1eaS%FIi0B^MjL;wH) diff --git a/libs/common/pytz/zoneinfo/Europe/Minsk b/libs/common/pytz/zoneinfo/Europe/Minsk index 801aead7d245b98f0bf90ec9d9a59d1bb53a8794..3731e40d8634c4d58ddb3378d828c92948f06d67 100644 GIT binary patch delta 133 zcmcb}HJfXKI4=VbfC(V&GEt>u;+!SYj7-dIEUYZ7%zr)E X^klOJvkN0JnmM^_v<-~ybd9+Hz(En# delta 154 zcmbQub&+d=I4>^)0|N+yfZIfs5*F2Y#Q_s%EMckt|NsAFUlwUb=E;eSa+4caxEOgR wPhe?+DTZj7%*cdh#$;8dCtyQ1vod=ya)6ZKGD-|$29)Ho(Kax)(>3M-03gF2*Z=?k diff --git a/libs/common/pytz/zoneinfo/Europe/Monaco b/libs/common/pytz/zoneinfo/Europe/Monaco index 686ae8831550bb0fe033409c8b4df460fcd61a04..7d366c6098c49ecd546e1cc1538919e1414a3aee 100644 GIT binary patch delta 621 zcmZn=pCmp(T#%Q6fdPa;UPdK;8S>XI66M+l<4SW7(|3HID;{m0~BUpU}S_~FayYk07k|M;<6aQ29%Tk s4+PmKXP@6(!ZL-C1r&&rW!O}iK>=sO_LF(=23!o#^)w5($&uXZ01OsPy8r+H delta 598 zcmbOv-XK0fT#%cAfdPa;U?&iBPt>>|b4on=!>LCh4^FSm{BY*Ex4_wz;uFp-auzs0 z$wXkY0OMaqMz+b0TpE)Vm^tcMSYeQZor9B$-jD}oAp-;3o5?4bWqf257zM%J;bmX| zvc(t}_yicFk=Z`JAq)XP9PSznB0w0(a|ZF8fjkBVMn`O%`WU-R#2lg?YpTRyMaf02N?ZzyJUM diff --git a/libs/common/pytz/zoneinfo/Europe/Oslo b/libs/common/pytz/zoneinfo/Europe/Oslo index c6842af88c290ac7676c84846505884bbdcf652f..7f6d958f8630cba512d8e58ca8edfbd516291522 100644 GIT binary patch delta 761 zcmX>k_)BntxG5(C0|N+yKtB+J*g_3JVm6O>!tATJKFqnfZo=FpTPMuhIbp*5mgy5t za28IOXd_{AaoaS8OA-4TE-myIxST9K;flBNgsYYp6|SZ~pKx_WF~c>@Edtkdqy%no zP7}Dvba2AWmlXmVr(9;NXJ&yzRz?t;4NS7IbI=8vqgcbhz|#TrEQ5>!BMSoqp8&}J z$ZRbGMqUO65II?ZSyI5+)i(sBoRJZT7#SEQ8!*d-VhzIoK#;#E@dt}K|53l%|&!G$e D`9jF! delta 681 zcmew*ct~)9xGFmX0|N+yKpzl;*t`uuVz&BYhS^uD6HaXRO_=B-;czL5UEuY4g$Zv~ z_z1k6VJq;iLr>s+g}lIr3;}_U5sU(#+&>9?Hh(PeMg5AvSMj3)-vmBP_^!_;@cqN3 z2}0+lf7m$VGGjd>GYb~T#L7k=sD*3|0|S=`(4P!43XCia4159%oG=n3GFg#X5)>AU zKrq>hSY`TqIW7eAQ}{4U>X=`AQ}{KllL;4)Pq6}BmfFM5Df}J5Df}N5Df}R z5Df}V5Df}Z5Df}d5Df}h5Df}l5Df}p5Df}t5Df}x5Df}#FbxcGkjFrw4x~0muqdle5qN2ZD(S0sfoYS-9B&xIhfC delta 29 jcmbOvK1qCnF=O{e6Au>d6JFi_fnZ`nK=vnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CF;GUk@a iOe_(blUQ~$bF;FrvH?|s;A8`ig2@gXY?BvoR005p#uQ2b delta 93 zcmew>I8$hXGULRFDh@0c7i~_Mn4-fX@HVAjudeE5=Ea obC_Ge3Mao|jsdIO?8362nU{%`g_RAc8U&dq3viT7ZsMp00C$ZV;Q#;t diff --git a/libs/common/pytz/zoneinfo/Europe/Riga b/libs/common/pytz/zoneinfo/Europe/Riga index 8495c506e8cecf39abac3f8322a8723184ae6f1c..8db477d01736445cafce8af7a7085d226d81f546 100644 GIT binary patch delta 85 zcmdlaI8AVZGGpCDl>-vSwz~yPf~EpGl-6l6M iaZO&sbda%ba~^XI-V!k6%47w%sjY5;RS~RCca_nNfv++248r3+X8nEOxzru<=hA xBL_1t9|$r}p1}TMvI8?v>*f^Z8b%h7=E-+hOjtlFH}kO8F~OB?p1{t=2msyCBP;*_ diff --git a/libs/common/pytz/zoneinfo/Europe/Rome b/libs/common/pytz/zoneinfo/Europe/Rome index 78a131b9fff014c3ea895a9cc7bf4197462b6602..32b2899a306dde401fa2e3952d06f5f4d9952bed 100644 GIT binary patch delta 181 zcmew@a#3W0GULjLDvs=noCVHLG7*?qGG#Ll;{`?mRz@Z`WM*Mznf#7Pb+RIJ`eXxU z-pL)z3S4{w44e!MG75|=lQ%Kj39e#bVBi2^!T&&Dr}4sPG9ybVH_%iNA7t|8i7Yyd zn~$>2WtL`RVunK&Rv=_z29ZF>Jh_omf{B%7@{GR DX#*+Q delta 206 zcmca8@>^trGUJ+wDvtb3B@fOmauzs0$wc7%<6{pd=1gG`Iye2pCdLblLQISZ$jrhB zM3WydsWLK67GTbtEYHk6xsq9dOGbf_g@J)jfPr)JVrDzRwG0dl96&7i9|&ys$(@<} ziMdo7XbXrBvI#_kYy;Ck8#foR=rDrq+&qhQA+r!O69TfZG6B(KZ%$Q4=E;Se#f06%Iz0`v3p`PhxSHY|oN8`3fUYbn_2J4kiu;AV5eJ0RY!q5)c3Y diff --git a/libs/common/pytz/zoneinfo/Europe/San_Marino b/libs/common/pytz/zoneinfo/Europe/San_Marino index 78a131b9fff014c3ea895a9cc7bf4197462b6602..32b2899a306dde401fa2e3952d06f5f4d9952bed 100644 GIT binary patch delta 181 zcmew@a#3W0GULjLDvs=noCVHLG7*?qGG#Ll;{`?mRz@Z`WM*Mznf#7Pb+RIJ`eXxU z-pL)z3S4{w44e!MG75|=lQ%Kj39e#bVBi2^!T&&Dr}4sPG9ybVH_%iNA7t|8i7Yyd zn~$>2WtL`RVunK&Rv=_z29ZF>Jh_omf{B%7@{GR DX#*+Q delta 206 zcmca8@>^trGUJ+wDvtb3B@fOmauzs0$wc7%<6{pd=1gG`Iye2pCdLblLQISZ$jrhB zM3WydsWLK67GTbtEYHk6xsq9dOGbf_g@J)jfPr)JVrDzRwG0dl96&7i9|&ys$(@<} ziMdo7XbXrBvI#_kYy;Ck8#foR=rDrq+&qhQA+r!O69TfZG6B(KZ%$Q4=E;Se#fvnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CF06%Iz0`v3p`PyWE{Fj=1^a`F*Ipz!7wj2uiH3_yS+mjnO;x)V?U diff --git a/libs/common/pytz/zoneinfo/Europe/Simferopol b/libs/common/pytz/zoneinfo/Europe/Simferopol index e82dbbc78647086170f0d291ee449122ed18e875..4bf24de1d9f8ebc410f120aa83d98b7e41d1e6c4 100644 GIT binary patch delta 363 zcmX@fy_b7}xS#+70|N+yfHx2eOw=eaQ0vwEpm~*}LF>rR2JJ1c8g!Q2ZqS`_ra|}6 zu?u=F>kITt78DqyOe`>rU2wsuXc@!A3#;{+xVV9kor4nyd3XsyzR7DCU0Gxl7&s=s zXLMm_1Of&|hRGI8YM3sXoX_+i2IM4=8$r$j(IBURXpr+jG{}h{8stnc4Rk7q200f* rgPaVaLCyxzAg6<9kn=b1X0~Re$OE!a4}eUB0-%Q&xO{`Xb&a_IjU-1r delta 396 zcmdnXeUf{ExFA0R0|N+yfG-g9Pt+)|P@PvCpw_GRLG#L+1g#_I1GKlCOVC-eF+g|9 zh6LS1$1do#v^waQv;-KWq&pbKF1TP+w2Z;%WZi{{CsrFWGjV|+J2N{oCkHb(4vnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CFpFjruLxF9D30|N+yKrIk+PSlW@=#jc{V=ZGnBMStwvY|n!I9QgQgW8aRLG%vD zIRY{Y3@i)`d;$y{3=A>~j69Q%GFkF60s%-C2pAbBD6`f98C;W1nH?tAGN0V+$MT6u om5CJs+1Sw_R2(cjIg!b2ay}E!B)iYE|cTg`6kb0l49W#VBna%mC2Hyfe{G6<^u^vMxdSCKn4hdKm!nSPgZ7j09!ga zp7|`u*3IfH-tg_3@i)`G75|wFcKu<;~T=@>$$>ly^c=*aJYeqvNstK0yTM@Z MBD@Up^9>Fq0MAD_fB*mh diff --git a/libs/common/pytz/zoneinfo/Europe/Tallinn b/libs/common/pytz/zoneinfo/Europe/Tallinn index 3a744cc6f28f3a886952ab19c8ec93f0e66cefcb..b5acca3cf51e7f7b3176965748688ff41720246f 100644 GIT binary patch delta 113 zcmZn?d?GMGT#%oEfdPa;pc;tzCu$ts_%WJsvMe{ diff --git a/libs/common/pytz/zoneinfo/Europe/Tiraspol b/libs/common/pytz/zoneinfo/Europe/Tiraspol index 5bc1bfeba62d14ffa8995c1d08ba7500956c494a..5ee23fe0e59f044598675db44d53c20590b88934 100644 GIT binary patch delta 81 zcmZn>z9uw5oRoqqf+lZZ;+p)4X&Snc&3l-qGH#A!fdPa;pl_neS>}Wn2^&8wWSktx5j;78gKP3zrfGZ-X&5=Vky#Q| X-{$4aQyD=zH=DBxGUL-~!6^p-VR0Cw diff --git a/libs/common/pytz/zoneinfo/Europe/Ulyanovsk b/libs/common/pytz/zoneinfo/Europe/Ulyanovsk index 7b61bdc522b5b7f4397fdb9246185f4d972f4b6c..d668233b37f268a745e995177195b06ffab1e69b 100644 GIT binary patch delta 45 vcmey&`IK{lGNaQ*6#>S{7K~1ls~M*=I&Btal3;>zOE|e~v<*z`bWOMbGLH+m delta 53 ycmaFL`I&QqGNbcG6#+(;`v3p`PgY}bnw-v3I{6hNPwG*_Lf%-I!kUh=uSD)px3guLBC{OgF(vt z2E*9X3>#1QGuE@Qu(B|-Lm&qy0&;Qlz#uOl)gc3e%p9PP7-Z__Ft7kA1qKcV1}y^y z9tH+J0R{n(2%`v$v~XaQgvr9#jIsz_sB9nK5C(4`c6AM5@D286a0b%OuE8LV3s4Zq z00BluFlJ7ZBu(V&(V!Rs(V$oX(V&huufotxFs(G0|N+yKot;!*m4a(;sj^mgfq{*1pVI)LEMu9;TB*G{OBS9iQz99_Gt{@y7!r&Y10>s`7u0R1- zkN^V%BO{amGe8!D03&0_WEEyHRji@@9|*GEZIA%bpwOS3&nzPY3U-hHDCog7F!(_< zC<-R`Gf#;D#R14@P&|NWP+Wj$P<()BP@I5hP`rR>P~3oMQ2c;sP#l41P&|QXP+Wm% UP<(B!XPM13!Xu1pvNne-0K-v}cmMzZ diff --git a/libs/common/pytz/zoneinfo/Europe/Vatican b/libs/common/pytz/zoneinfo/Europe/Vatican index 78a131b9fff014c3ea895a9cc7bf4197462b6602..32b2899a306dde401fa2e3952d06f5f4d9952bed 100644 GIT binary patch delta 181 zcmew@a#3W0GULjLDvs=noCVHLG7*?qGG#Ll;{`?mRz@Z`WM*Mznf#7Pb+RIJ`eXxU z-pL)z3S4{w44e!MG75|=lQ%Kj39e#bVBi2^!T&&Dr}4sPG9ybVH_%iNA7t|8i7Yyd zn~$>2WtL`RVunK&Rv=_z29ZF>Jh_omf{B%7@{GR DX#*+Q delta 206 zcmca8@>^trGUJ+wDvtb3B@fOmauzs0$wc7%<6{pd=1gG`Iye2pCdLblLQISZ$jrhB zM3WydsWLK67GTbtEYHk6xsq9dOGbf_g@J)jfPr)JVrDzRwG0dl96&7i9|&ys$(@<} ziMdo7XbXrBvI#_kYy;Ck8#foR=rDrq+&qhQA+r!O69TfZG6B(KZ%$Q4=E;Se#f--Q+N4m&uEm W19(6x{{sO?^=3Af^^plx6b#3n#& f3RcVr1laU%{>aF|gl2#uE(5r1fX3SC8gl^vg}oMf delta 136 zcmey*F`sjSxF9D30|N+yfFlrdZq#65WU2rE|NmqIMh8Zg$%TvtYzzz*4h$TV7cxq4 fF@jZs2yB`+KV;-!!mV2vp_|J_+rY$5*MtiIcQ_Xm diff --git a/libs/common/pytz/zoneinfo/Europe/Warsaw b/libs/common/pytz/zoneinfo/Europe/Warsaw index d6bb1561db672f94bfd5bccc95fe1a8d18a158d6..e33cf67171da78aa9e6eb02e50f9b9603da4c3f4 100644 GIT binary patch delta 99 zcmeAWy(cn3nQ`ev70-!8Vqu*u!2FYO>0~aJ a-pTV>ZcMIcUBB6kO@|q`>T8p8xMTo&A0Lqb delta 107 zcmca7(jhuQnQ_HL6;I}dl?fAba+$UM7EHVm!J>HU+K0^sj0+eUnI>Ogl4fL{{E$fu pNHQ}2WLz=%H*+sY{p4nryC4mdqgl5=ba1m7Fk|SrHra_w1^|0vnXz)Bii*I6UyKD87xoBT@^hFV;Tm7CF#TzuQax`ci`PrboA`S|SA|MG6Y2m;q z36qAg8D$aLP}x4dAq?KWA(Nl8Yp^gfGB8Z$WmXBn8f^c8AZ*+56c7yxJ`fEGLJ$oK zMi31ON)QbSP7nhuuEWqxCt)<0|N+yKot;!*eVS`VwL1#gRM&>6Hcvqe&Nh>Z-KKb#V4Fw7>ILdwB?>g2ybaL2@+Lv+$oT;6E$0$+mTU~row6Z8uVr$8e#xE*1}SSN z7{=Zd*mxwJv7UpA2?Du!XaN}*6n+3*&Y*CCfrWuVMuCBgfkDfFfscWKPk=#)fx%ox zfl&-30U|9N7^Pv-FgBw+NCZTJH2e65FlZYX85vCe$gVM2j9CSHVEqSzux*DqKr|@O zKr|@eKr}G$Cg(BBG3~uJxsQ2n3@Fe*4g>`phz127hz129hz12Bhz12Dhz12Fhz12H fhz12Jhz12Lhz12Nhz14l=6aUROhY<^WjV9~;6#5q diff --git a/libs/common/pytz/zoneinfo/Factory b/libs/common/pytz/zoneinfo/Factory index 95bc3a3b1a0c21d0314dd63c80d4e2e8ac0513fb..60aa2a0d695ba577ff87624d479f1eb25c8f1caf 100644 GIT binary patch literal 116 jcmWHE%1kq2AP5+NDp(+@bPWs`Ldep^Wdqb}XTSvjS7imx literal 120 ocmWHE%1kq2zyORu5fFv}5Ss3H(f|Me diff --git a/libs/common/pytz/zoneinfo/GB b/libs/common/pytz/zoneinfo/GB index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/GB-Eire b/libs/common/pytz/zoneinfo/GB-Eire index a340326e837ac8dd173701dc722fe2f9a272aeb6..5ad74220e83e6c30a0aeefda4a00271b8ebdfcad 100644 GIT binary patch delta 415 zcmca7b3tZ;GUKO>Dh*7VS1<)IGO|o&=%je^@rBaLr*FA|6mh@&K2+Z-{|YaEOktv7V`( Ofxe-Rub}~m#RUMe=pe8F delta 481 zcmca0b5CZ1GUMlsDh*652G3O(HZNfcV4Tdzt1?-Dg>SMNi&Z@f8zU1sWM#t#+3`t| zDrW@K@c;k+tqefGC&0)8q8T_C7-SR}c|alzKoNxaWC2zgZbnA1A_gE0^5EuZ);}x| o9|m(RU>XKKR7LV3m%DF>fm3jZj<2zvsh)wpp^mSi0f@x~04z-^9smFU diff --git a/libs/common/pytz/zoneinfo/GMT b/libs/common/pytz/zoneinfo/GMT index 2ee14295f108ab15ee013cd912e7688407fa3cde..c63474664a289aa3c3c0d8b2ce06d484679754c0 100644 GIT binary patch literal 114 hcmWHE%1kq2AP5+NDp(+@+yiFHT@!@CXiJ2q8-s7f`FA0T%#a4Gc{H delta 37 jcmXRepCHc4$iTqBI8jE5iGcx$$H~CJusMH9nX>!qk5tu~~4cdz!TMb48LJnyhj~_eUG*MaWBS(BnDmND zlP*@7{+V-3|MLZAV5DGPm`U^Ue%fR%&sT%D^6J%z3HAEiUHPUyq23<5D&MV)s-dPX zot;&oa_c*FZhW&DUU*s$r-EU_QS+(0MUJ*UGN1J}`K95e`Pv?l z->M&~vHfA0Uvxti>c-^vrAakDcaJRkcdKH4gZwd6tA1vh^u&W*X7YB8{&lU|lrApO z;_PDMB>mcR&}Y1jIqi#Ojla$(1FO9%xGXD!C)cX7K({O_bg5AGik#ltsb<_cBWGWz zQsF}_+VOaOL61LRx986edZxIAXb$nE54Q=Q5XqEdKX+@pQ_fk0|9;LpTVrvtDay5- zTSHuIu?OMUpK$CA?TSxfv?aS3FEV0RJ7#3m$heV_BV$KKkBlEl0Lj4Brhw$&YLh^+ zK+-_+KoUVRK~h0-L6SkTLDE6;K@vhTa~nn<2VqDZFbrpkd_SvFZ9TO?g1UnF59V9sl#(L-rq0p+dg_>2eu! delta 553 zcmcb}Ih}KYcp@tU0|N+yfGZG#*n+K@QQuC!3g2<+meq>W-W3g(e3A+-XKlB*GTCdz zRi_OW*JkZpaox^w#|=G|6*qob?zqLrw&K=J=^eMEKr|>UKr|>kKr|>!z%(#i zKr|?9{{PnlGJpXD5fhz11|hz11~hz121hz14P=J`w$7za4mxY&R}scXRn0CdE| AC;$Ke diff --git a/libs/common/pytz/zoneinfo/Iceland b/libs/common/pytz/zoneinfo/Iceland index ac6bd69731d25fc20724798abd4f683f1f2ccbd6..28b32ab2e0b9053f39a91d9f28b6072e41423954 100644 GIT binary patch literal 148 zcmWHE%1kq2zzZ0GvP?kCG3nVP561uh|5!kkv-tRiFt`J82nmM#2LhZ1aRE&;-~s@2 CSQl9U literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}ZK$jMYysKwL@$Wb#X)mML|W;t;7K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3WN{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT diff --git a/libs/common/pytz/zoneinfo/Indian/Antananarivo b/libs/common/pytz/zoneinfo/Indian/Antananarivo index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4Ja@j={6L8yJBi&QCyk+yWR_7#Knl7&v@Y^A;C1D XLH~=zRz3mg!*42=4bXf$U2`q~*SHut diff --git a/libs/common/pytz/zoneinfo/Indian/Cocos b/libs/common/pytz/zoneinfo/Indian/Cocos index 58f80514e13d1929df97047c7a3de221640d93f1..eef37b42e8a0e7179f8113bea01f4a71d668e8ef 100644 GIT binary patch literal 254 zcmWHE%1kq2zzbM_vaCQX)Bq$Feku+;{ppXy#aZbe7@3%v7#O1Tfx;jXs4hB#frEje zZUO@zgze)S!Vm<++6HFE20+r1A%q0`{sTeSw$6(n8e}hs2H6dw$+e%$2J99)T{A0V G11lTg02q%N)Bpeg diff --git a/libs/common/pytz/zoneinfo/Indian/Comoro b/libs/common/pytz/zoneinfo/Indian/Comoro index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JK~rL2o!O70px&476t~l00s^p-w+00Al5c8We6d` Y%>O_Twr#!yh$hBtE*qc?cDklq0OTbe`Tzg` literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol(Vp2o>|Ns9P86gr33~m7oEV>2;4B7^!V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&r@01o#Tp8x;= diff --git a/libs/common/pytz/zoneinfo/Indian/Mahe b/libs/common/pytz/zoneinfo/Indian/Mahe index 49e23e5a0a8d5e90a9da0838a08c17c20eefaac0..b3ac791aef4e73d6d644c40c614f37f15d462cdd 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCvGCK(6+n*h4UlpR2L=`&-w+0E0~3Z25)And1UL=ivH==r Ir)$Cm0Qy}Np8x;= literal 173 zcmWHE%1kq2zyM4@5fBCe7@MObh7b}= X0~+)nWFkl(ep9(@facrjns5OC04Eyn diff --git a/libs/common/pytz/zoneinfo/Indian/Maldives b/libs/common/pytz/zoneinfo/Indian/Maldives index ffa33658444a473605f9a04a78f3ec345e0fbe7d..555728b1a0187cc0ac63b8fe45c44bd1e0957918 100644 GIT binary patch literal 185 zcmWHE%1kq2zzdjwvdlot(*Pvi>K~rL2o!O70px&476t~l00s^p-w+00Al5c8We6d` Y%>O_Twr#!yh$hBtE*qc?cDklq0OTbe`Tzg` literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$a$-Oct-vI|Nj}8m>3vbUV!9*BntzBTL1%xk8cQr rFA!@Rn1UpLAcO?tfu{Zs+csYUM1!mW>8}T}iL{E#257CFt|=D)o|z^D diff --git a/libs/common/pytz/zoneinfo/Indian/Mauritius b/libs/common/pytz/zoneinfo/Indian/Mauritius index b23e2cee1f1bca4abedd105b04824431f40e8392..212d4b2e2afaed06110a1acff4fdb6bd6103b4ff 100644 GIT binary patch delta 92 zcmey%_?U5mxGn<{Ustqj+eHSR%+nrtp6kEB#K^=r@x1&5ab6@H W46G9;8L&X5IJs=J4NUBGO}GG7#}X(2 delta 119 zcmaFN_?K~lxDhi00|N+y02>g4*gT!}tTQB7-#SR#E;8_Bp7y}=T>pjo|Ns9pF#_Sl l-SQxXKrlg^7e)KTb^~6JII>cHkN^V%myNc8iJh(q7XT*<85sZo diff --git a/libs/common/pytz/zoneinfo/Indian/Mayotte b/libs/common/pytz/zoneinfo/Indian/Mayotte index 6e19601f7d3a420c1d4832178352c1eafbd08146..9dcfc19c56e62b12b730f4335b34479695f273f5 100644 GIT binary patch literal 265 zcmWHE%1kq2zzbM`vLGzd{r}>hjqh$nY&rhm!ojy|BhKVhU14NmWM*PuP-+1gp{&8c z!oZ+qz`(`8ptgpA55o5G4PnqWFfuk^aCHQ;OiURq3(N literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J37&07mc_c>n+a diff --git a/libs/common/pytz/zoneinfo/Iran b/libs/common/pytz/zoneinfo/Iran index ad9058b4937b8786f58e2cbd61adff5ffb6f0657..cc2a2c219b0c893cfa8187e962028eb19ea3425d 100644 GIT binary patch delta 468 zcmZ3%`+#$TxG4h?a0jwEfLLZ$PVI^P3}-xer?^<~trgzEUs>!RpmFMez*{Q^!L@rl zCVKp-XJlq#WnpDzq7X7L7+8Ru03=x$80qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSmmjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)56jzBYpzV*O<5f diff --git a/libs/common/pytz/zoneinfo/Israel b/libs/common/pytz/zoneinfo/Israel index 2d14c9998e9dc54bd1ad4710332abafc8e811242..1ebd0664aa29c0abd722661f761031ec0304631c 100644 GIT binary patch delta 923 zcmd7QPe_w-9LMp`v-aoFJgEH=V4#CiD(FyDw5XFZ{B!VuXr~YlVjvs-AP=Ib z!9%w&2pI?pi$>68L4jMK?okKRrEUJJ)h?ZE4DIRnSzfz%2%m@F@BMwA8{aPfIA7w0oVZjj_{h z;uG5~-PoDDD|XM`z}LQ?_*Stjvh}kfn;n4aAI+!-A){pPhgKTAWSsiGubr;#(#{p; zwDaRVnlqBmTzKMt9et%(>=u2~;Tp8rZFYz1P}%?U0=0H|XmI6Wh3;3Yt^!UM%76y9 zqW+!lKWM`}xjeVzYGvgBD2l@EZ~*>=V#>$+_CtXs4oje7B@#<0mRKyoSfa6nV~NKS zkR>8ZNYlJyn$+YEiWVzXS<151Whq>&RAwp7Qd?K8jn{mFpY&yE%+i^qHA`=n=EX{P nz1LcIk=Q&}g0n^NujOF-gy7WEPn}c14GMN^F$T_)vu$Pu1^Vv~IgD z>$+K@H2a!(4zOfUB3HX2yJidICKy&yYkLawOpx1wX>u2G%4tWNMDLeBNyciW593kx zXo)Dnm(T7lJ$046UwpLxwoy(m#aT(y{N1$pY2zS@Ghu>Z;zktb1i42@=V;QvW@ Pi-6JCssE2LB$N09QcoF* diff --git a/libs/common/pytz/zoneinfo/Jamaica b/libs/common/pytz/zoneinfo/Jamaica index 162306f88a80d69cfa5053103c285e2e8b221c3c..2a9b7fd52d37a1ffe9fc589daa04d88c6c71a6e0 100644 GIT binary patch delta 33 fcmeyw{D^siI4c7POq5v$XFmJK#^o9uV#);ok533T delta 50 tcmaFF{E2yjI4cVS0|U!MnPu!i8U!XDlz@ue`^N_nVqoBM4GuBo0ssLc2@n7P diff --git a/libs/common/pytz/zoneinfo/Kwajalein b/libs/common/pytz/zoneinfo/Kwajalein index 54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5..1887a607422edd499fdf24afe91a04294f1caf6f 100644 GIT binary patch literal 302 zcmWHE%1kq2zzf)bvLGzc03;5+HN9~16<5M}x5f|u+oS_@%J>`@nV1<_SQ!}RJOC6ty}=S6iFEX literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|vRgu&M}1SrVJ2vWFNl=&eGL6wuG GIAj2Xfefes delta 127 zcmZ1{uuWisI3vqsL1vMSI%}C!7#J926c|}xB%c5SgRg4{5C?|yipF4qp!8bUBA%rYlTtKbnTmW*V4CVj; delta 36 icmXRan;_20$iTqBI8jE5iGcx$$HBnB(^b diff --git a/libs/common/pytz/zoneinfo/Mexico/BajaNorte b/libs/common/pytz/zoneinfo/Mexico/BajaNorte index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..63dfdf48a68d02240737ecd6af081e02eb0b6317 100644 GIT binary patch delta 551 zcmZ1`bWCW1xFiPy0|N+yz*Haxu_cyndr`2_Ac(1+nTdsol?_M?49vl1?f?Iid>9!1 z|DQX7f#v`I{ssmP7<=vnMjjBGQ2mv`lD%xFj0`0|N+yz*Haxu_cyn`_{10Ac(1+k%^g!g%wB)42;2M?f?Iid>9!1 z|DQX7f#v`I{ssmP5Sx()ObUQW5g*?W2H)Tih5!(D0pf5V4hR8xl7WE{LQW8u#p?P0 VKmZDW%?p_qu?*`Fn5@Ss0RTUKN9OK>GD8$ literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJfgC7JuG-)X4V~ms?y}Zi%;Vx_w;TA(Apg zg~$pK79uS~T!_37fgut*v_*!WxF`n$0|N+yfC~_V*b*Cc1Q?wenOK-vm{@_34NQVqB*Mw>Sw-zuFJR>P z|9|cT2A2Q-cWz)50FxsB|F2%az~SQ?!r&Vm!r%GABiuHHy{bpaC5Q1Y9>UeJ~_`RAn*%1*_ zy}C>`jM%BxSDE?<+tc4gnSN}tXI{=^rtoRc7VpiugOWXea%wJQZ*6mD*UToK#bPpJ zF7@1qD^vv-6(A)TH6TSG mRUl>HuR7H@u^QFs`)?hWiWX7P{6;ts3IzQ|C>ZDt1YJKqTAaZE diff --git a/libs/common/pytz/zoneinfo/NZ b/libs/common/pytz/zoneinfo/NZ index 60bcef686badda46f36652694d06d041c70a9d87..6575fdce31183d8238b18f2f30ab5b9227c7071c 100644 GIT binary patch delta 51 zcmbO%+$ualoRMLpL<%#A-aMQ63ac!aUsQ02uAz}%luL+?uce-;o`H_9iJqaJfxa;p E09{QCcmMzZ delta 40 lcmZn_o-8~;oRNK_L<%!I0|bEho2M{eVP%8xC#Q4D0RW#S2OafdPa;plPED4--rM|Ns9d8!$Ca-oRu9kwI6snU7h3g#)AmSuG1l JVDfc#eE>{&6Sn{W diff --git a/libs/common/pytz/zoneinfo/Navajo b/libs/common/pytz/zoneinfo/Navajo index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..abb2b974a47eb3e5c8b4f5d4370baf4898b239ab 100644 GIT binary patch delta 145 zcmeAXo+CU#T#$`{fdPa;U>*>&ZPch?;$~)Kf*>&Zq%q@;$~umLS~l98<{3fw&N6=9K)>0&cFy#$B0ee n=5@?pSa4~Shic^V4GuB)bqUe&HP$oIGtluhG}JTHGvERMYMd1i diff --git a/libs/common/pytz/zoneinfo/PRC b/libs/common/pytz/zoneinfo/PRC index ce9e00a5db7c447fde613c59b214e8070f30ba2f..91f6f8bc2e234bafd484146986bdb289082c3588 100644 GIT binary patch delta 111 zcmZ3;vXNzixFQ1*kOi`tftaTONGzDXs9|9x%ZG^u&6B?{Doha91q#9eT$@6951J diff --git a/libs/common/pytz/zoneinfo/PST8PDT b/libs/common/pytz/zoneinfo/PST8PDT index d773e28f1e7743936188263954598b2b521c15b4..99d246baa35cb9c6f56d50adbec163452e2a47fa 100644 GIT binary patch delta 161 zcmew+*d{bVT#%K4fdPa;U@8!^Zq)e1WX$sa|J(_T3}BK2O!EBy-`~K%65tZT5D*N+ c;XoV^0@B3@A+VXXIhWaul^nw+-{X(~0HyjWga7~l delta 145 zcmZn@`X)F*T#$up diff --git a/libs/common/pytz/zoneinfo/Pacific/Apia b/libs/common/pytz/zoneinfo/Pacific/Apia index fd03ff765eace5b92fbe13e3f151a1bc49c88d03..e592a68e53f6215de9e1d1ac61ce062a333283c0 100644 GIT binary patch delta 70 wcmaFLagAkyI4=VdkeaAcFj5|*JkRS%lm5x`d8a!2vn$LW$87fw zCr7(6q(j#=ZD(Z5yED4=%^m4@oYT(fE3)m@Y1w|m*Bux7r0eXsb`Ne4$>nutYeX_l zY3->glU>Ua+FSlnvh&&{`+1J`eOfKK=kK-uS%vJr_f+@z-(>H^ZQXbAwG0$4>)_C| z42duMJ6`F5#&H?0yRZ4hc{x}ysUs#WBVSHxK@)Q5MUNhSP$@^I*6GoktMu5ljEtUJ zsK-yOm9gAc9kbR3W(7-vC85%Pc!iAd-Q%*h+dVUDyP8bU+F=QVt?g|y<;L8dG4qXi zq=5y^$&{(~yjZQfa(X7>#oQTppZxtu{l!x5&)b1DPuwqC%VNlCd>_jp>mdskbt@uE zB5NXxBC8_HBI_axBP$nmOCxI|izBNe%OmR}1t1k5B_K5*MIcomWgvARg&>t6r4)6w zAjKfnAmt$SAO#^6AtfO-Aw?loA!Q+TA%!88A*CU;6?MfS)gk2}^&tf!6(S`fH6leK mRTgzy{$FK=>s_%8)n3dQZ>UPvMOQ{^YhqQ&c+_(@@$fIA@G;;3 diff --git a/libs/common/pytz/zoneinfo/Pacific/Auckland b/libs/common/pytz/zoneinfo/Pacific/Auckland index 60bcef686badda46f36652694d06d041c70a9d87..6575fdce31183d8238b18f2f30ab5b9227c7071c 100644 GIT binary patch delta 51 zcmbO%+$ualoRMLpL<%#A-aMQ63ac!aUsQ02uAz}%luL+?uce-;o`H_9iJqaJfxa;p E09{QCcmMzZ delta 40 lcmZn_o-8~;oRNK_L<%!I0|bEho2M{eVP%8xC#Q4D0RW#S2OafdPa;plPED4--rM|Ns9d8!$Ca-oRu9kwI6snU7h3g#)AmSuG1l JVDfc#eE>{&6Sn{W diff --git a/libs/common/pytz/zoneinfo/Pacific/Chuuk b/libs/common/pytz/zoneinfo/Pacific/Chuuk index e79bca2dafa7e914a9baa568edec1b428a2ac63d..7be2474dd91c8a7da181fcda09d838254b890d75 100644 GIT binary patch literal 172 zcmWHE%1kq2zzZ0GvP?kC(EucXoX#IWnxSU_1B;Ju2!pnv0YeC3LqG;F12OM^APCzQ n#Q~y=-Y%KI2$X@C*!h8h1#B)S$lL&5sNq~TKr8HY4Gp*eA?O^n literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|ftvat@6A23!-KEU2R*@wetvI#R#W^)8{E;Cr_1ltFwR4xD) CWD`^X delta 81 zcmZ22xKnU~GGot1m7R>tB1#7)KVYn2tN;K1KO@WJ1SZkR`Aj8~Z8>}=>oWrtZ4PG6 SWd^G{%=Uqc0SGWu<^cejdK>Qm diff --git a/libs/common/pytz/zoneinfo/Pacific/Efate b/libs/common/pytz/zoneinfo/Pacific/Efate index d650a056d9e7c734f886bb4a82d2256898140458..777325fc6c6da8795d89aed5206c60f8bf80b0ab 100644 GIT binary patch delta 249 zcmcb|+`}?KT!)o`fdPa;KpcocY@SJ94{c$uoaw;+>x_fsrSBJ{4&6E+z3JveYgbJc zMkZz!Ovu16_YBC06$cnu7#L=4VBnazK$4S@5hw&ACWyOWwdg+(fGh)P1z8BDftG@3 iki{SxWI2ciI$$y3Fic2%hV|NlP|BNoWS zz%cg=1H;6Pk|3=UFG`wVH4>=(Kgd|18U~QjAR1&mhz43QS&y+(1;i%89)6HI1}+a9HPdgHRaD0ex1>4U9}6VIcVbKiq+V<^TWG00s^QhHV!Z ucytX67<3JdfY^{h+t8RHgaj-80|CeykjWscKs1TgaoGSZwbL~;<^li|ek7{^ literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$Po%-IiSyKxuO35|No3k%*_A)$IoG4`2RoLfq~`! z|I`2m4hDv87Z`Yad_x#?4UK@{J3#tC)_`adtm3i(T5G3kXv_rwq0K43 diff --git a/libs/common/pytz/zoneinfo/Pacific/Fiji b/libs/common/pytz/zoneinfo/Pacific/Fiji index 61a669535f14d380929c8fed6708b5ca9f8d5bae..acf8091ac85151a6c887e2b5049ee40754b7b0d3 100644 GIT binary patch delta 156 zcmX@av4uq?Bq}q_hygEL7&Dq3EJ5w~1Mn)TPTF68j@Md-#f|%&Ex|)Acau&>^8q z#6svYqKC^MN`)X8BZwkMND!jnOCUj=k|2<3ec!)prwICuAFr|Thq2GwPo{<<-XCX! zeZu7&wafdhb2}cDTHEWib!A=J_OwXb;(Lj1Ytm@3Mq=g963-mdMB%lZ*!f&LuNI|q z{hoHEUuoC7?2_+)694IlcDKBg?zx!uG^}b*HmJQnUuZH}t9>6H>ZyZkdivR{p4nX^ zXYY)u1iniDwd>OV@vRIDjmkiIUIvdRWU!Ez;l0f=Jo{Kvo3=|TeM?7HzezfJK}X-z z>R4m1ju+ST+?IBoxUsAgUm7$szMz@qLo%7Tt&>mcW$M7ZOx^q?({*{7KCd$KeMYh; z%W`q0PcB97%H_o)a;3hcx%@84{U~UzQmF*0{^=F)y!$2lAH9X8s*RrapyX|i#eJ_a z6tNrT3p@T_>|sBt!X`Y&E>7D9*~e)cAv-y3D`YQZGh{bpJ7hnnZHVmXv@MZ6owg~m ztJAhc_I29E$j->t$ll22$nMDY$o@zJNC!@90qMbMO(0!3tqr6Pr!|6f;ARPho564}ygw9XadxGoC=0|N+y04orK*gV^o_%z5#90|}C=X#)B_$8qJ|NsAt%uLJ^?NvEI h3PE6kI4_Fci5(g|AaNwM0w6vJaM|b@nAw?e0RTzo6~6!g diff --git a/libs/common/pytz/zoneinfo/Pacific/Gambier b/libs/common/pytz/zoneinfo/Pacific/Gambier index 4e9e36c5a7edb3ebbe08ccc56308ac262df9f58f..84acaf41520d2d302f75e300e2b47c5527218df4 100644 GIT binary patch literal 150 zcmWHE%1kq2zzZ0GvP?kCF(tr*h4KIY#u^3&AX&h`;^P~_ple{s5JG|>|A7FfL0mRK I?YJ1ETB9bbUZH$V(s^K? zWe@;)8$^S=4gw(WgJ@6?fN5YbfM`%qfN5ZGfM`&VfM`&#fM`h2Ff*~BK~`AUv~K|^ PBO!#iK;fioXut&kj?IX8 literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF diff --git a/libs/common/pytz/zoneinfo/Pacific/Kanton b/libs/common/pytz/zoneinfo/Pacific/Kanton new file mode 100644 index 0000000000000000000000000000000000000000..b1c4b0734483033722c28c8b2884ac9dddd2ab45 GIT binary patch literal 220 zcmWHE%1kq2zzdjxvMfL>a9HPdgHRaD0ex1>4U9}6VIcVbKiq+V<^TWG00s^QhHV!Z ucytX67<3JdfY^{h+t8RHgaj-80|CeykjWscKs1TgaoGSZwbL~;<^li|ek7{^ literal 0 HcmV?d00001 diff --git a/libs/common/pytz/zoneinfo/Pacific/Kiritimati b/libs/common/pytz/zoneinfo/Pacific/Kiritimati index cf5b3bd348fcc7d6a39dd6b64b1658046444f71f..b4c6037a2d2a8f89539c3df05c32b6f52b1b1e92 100644 GIT binary patch delta 82 zcmeyz_<(VOxH1D0UXadxCRRY0|N+y0E{iv0OSaTeqzvP)m~8l|NnnRCgzE&wK+g)KwyG6FH9+j c%QCT3fg8j?C=~*6L4eCf+t9>L*U*Fu06?A-O8@`> diff --git a/libs/common/pytz/zoneinfo/Pacific/Kosrae b/libs/common/pytz/zoneinfo/Pacific/Kosrae index b6bd4b089416a12b02a37eba8a1082dfa28b7797..0666fb0dd161cb732b29f84d49c49cc985a3559a 100644 GIT binary patch literal 337 zcmWHE%1kq2zzaBlvTQ&s(f}l82u{7Q!1CvZgKteQoP5QVa7mEw!~Ztv0JH9N2Sz4F zW+p}!#{d7_eSk_CW^G_#VPL46zyLI%X8{8b1H*~~3<3~wAKwrLZ9_u_Z39anZ2-hZ z3?YQw!U;5%9f(E#1A)lHEn7fzotcaYhz7Y0M1$N1qCsv1(I9t%Xs}yB44`{K?t{3Q dnTds&iIoZF=ox1~QlvVa%LeEfJ6%IVE&zWeV1obv literal 242 zcmWHE%1kq2zyK^j5fBCe7@Ma7$obzU9bnd-?oj{#|9?g%Mn(pP8D~I>W^DkeTXBGa z1H$(44PnqWGz4OV=^-T81vLJDotcaYhz8jSvIAr`@nV1<_SQ!}RJOC6ty}=S6iFEX literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|)O|9?g%CI*HDAQ6UH8yHv^7*-r$;PCMc rVbC@-1Y($xAtV?NH1&U-nMebO23Z3#yB^3S(kd<+ptW|ohDKZfj`Jm{ diff --git a/libs/common/pytz/zoneinfo/Pacific/Marquesas b/libs/common/pytz/zoneinfo/Pacific/Marquesas index 5fad0e1b201fb0cf49f3d2c27b117e9a029501a4..f546c03f96b24859521aab5b9997bfc5dd124ead 100644 GIT binary patch literal 159 zcmWHE%1kq2zzZ0GvP?kCH6_5ugYp0WS_=k-|NpBp7+8FKLl|@oER78qLP#*?KM>$C PipvIUq@AUeu>ltV5uO{q literal 181 zcmWHE%1kq2zyM4@5fBCe7@KQKfR9K0|Ns9P8UO#UwP0ZQ|Gz4OfyKu+ghAK9(%1m3 cID`cAfJXfXnF-QJ$XqTPupM@mR>lTg0Jwo7!TI4c7POq4N!GmC84xq^d33=O#eb-e~$ delta 47 ocmZ3_xSMf;I4d&)0|WC!851@j4FqtJ92;JU2v=}$h@l}D0ICxPZU6uP diff --git a/libs/common/pytz/zoneinfo/Pacific/Nauru b/libs/common/pytz/zoneinfo/Pacific/Nauru index 7e7d920e11396d3b210f28c1d69389bc68d33548..3339b6cf86d6e98ba70c9bd6cab3dbf50588acd2 100644 GIT binary patch delta 89 zcmeBSddE0Hd}5P`h|uEa+AL=#ygYHqS^a~c;))lHOpFuHYl9_Kg#H5oNCAijDFV?T Vg$zJ|P|U?;qitwpr)y}$1pr?0C#C=Z literal 268 zcmWHE%1kq2zyK^j5fBCeRv-qkg%&^8W;wlOzsCiqfC9m8mJ{{=|Nm!XVq|7!V3<<_ zQn_#kNMYRs1}+AM6$cpje0)O~v<(f74H&cyEP*V9MIj_u3$*1w$Z8M`vK~Z(oB*;B XMQ|plfJo dWB?)=LP#+6KM;US2GPWs&Se9%!_Lr<3jia|El>ae literal 257 zcmWHE%1kq2zyK^j5fBCe7+a(P$hqTenjlbe%A)@N|No3k%*_A)Cv0J00FnzBSpNTy z^I+im|35W=LBPj1ghAKP(8vHp8iPrYSzr)Cf}KEX{@0m@_<(4T-5>)%_JinppcuJs N;IaX_%g)e{3jo^+IynFU diff --git a/libs/common/pytz/zoneinfo/Pacific/Norfolk b/libs/common/pytz/zoneinfo/Pacific/Norfolk index f630a65d5778d651b9d6842159d593c3cc18a996..3b4186d61152629b764efc4222e41647b65f7fbb 100644 GIT binary patch literal 866 zcmc)IJ4{ny7>DuiXpyuFuqZgV3d*IYB?S^gVhjWlx@aLt0?PeVZZDu%<JPoo=yPM#*;?@Nc1^#3%`ksOGabD#X*-`=ot zzvkOx|MrO=EcomN-~RC03J$!^r48+xU)2Yx7y8i4Xm|Uh_B8crZ*8mgl{RXBag7cX z^~#`=kip{y@xI3-`Q^Gi`cN!GTR|CKJ(H2eBN_GgWlZ)&k|#QT|4=8Izv*P%u0Ae% zr%y`Obt<@`Q>P31^w8JT*KvLJsYjph+|%hdX_n47NaMmVEkdq#4n%Z*n!_DTH1 zm_O|^7rQc#jLJJ0Hm4mI85kKE85$WI85|iM86FuQ34laELLf16+8{_2Bn%P<34}yK zLLsq`U`RA191;%+h(ts}A~AE?ph#3CED{$9j6_C4Be9X-_+O)kZrOiCeTC~!p6i1gv=9v8(>JTaX?6NaoK1a8rta^8gc;uaP$g= delta 98 zcmZ3)w2NtiI4>&$0|N+y0LMfX74G`~|Nk>F0wFWYL_Y(F6o$G*4*VbusA|DN3|uza LhK6>!hK5`KH|G#V diff --git a/libs/common/pytz/zoneinfo/Pacific/Pago_Pago b/libs/common/pytz/zoneinfo/Pacific/Pago_Pago index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..cb56709a77dedb471150f4907771bf38f1879ba4 100644 GIT binary patch delta 34 fcmdnZxSnx>I4c7POq4N!GmC84xq^d33=O#eb-e~$ delta 47 ocmZ3_xSMf;I4d&)0|WC!851@j4FqtJ92;JU2v=}$h@l}D0ICxPZU6uP diff --git a/libs/common/pytz/zoneinfo/Pacific/Palau b/libs/common/pytz/zoneinfo/Pacific/Palau index 968f1956c8eefeb78d96651051befba0b1a3ab82..1cbebe28afd90c0a2e02786655e18a157284a412 100644 GIT binary patch literal 166 zcmWHE%1kq2zzZ0GvP?kC(EubE|NqyS0;C!0CNQx0_=YfO8(1=g5H}ea e0}#m&LV~IPfuPP@?+egyB2DMA0oq|_!36-UeAku-@w4- s;~T=DYhYn)03tzJfgpqg3xMYSuQS*C0@4Sv2t<=)8J7*vVmk{i0FU=74*&oF diff --git a/libs/common/pytz/zoneinfo/Pacific/Pohnpei b/libs/common/pytz/zoneinfo/Pacific/Pohnpei index d3393a20d85209df94887e0de0c88e08442e4009..1ab8353464ddb93947f871f07cfd12540373269c 100644 GIT binary patch literal 152 zcmWHE%1kq2zzZ0GvP?kCF~#4w2gsTF1*Ck|1_l-%-w+0ELqmoT5)And1UL=ivH==r Jr)y})1pozy6$1bO literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f zKq(Lg0T`PXXwd(#ZBZN`y6Eka2_VUOAe)g1Xd%S-&JPSMU`sebmIU}B*~GxWWdpR; JPS?u?YJ1ETB9bbUZH$V(s^K? zWe@;)8$^S=4gw(WgJ@6?fN5YbfM`%qfN5ZGfM`&VfM`&#fM`h2Ff*~BK~`AUv~K|^ PBO!#iK;fioXut&kj?IX8 literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF diff --git a/libs/common/pytz/zoneinfo/Pacific/Samoa b/libs/common/pytz/zoneinfo/Pacific/Samoa index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..cb56709a77dedb471150f4907771bf38f1879ba4 100644 GIT binary patch delta 34 fcmdnZxSnx>I4c7POq4N!GmC84xq^d33=O#eb-e~$ delta 47 ocmZ3_xSMf;I4d&)0|WC!851@j4FqtJ92;JU2v=}$h@l}D0ICxPZU6uP diff --git a/libs/common/pytz/zoneinfo/Pacific/Tahiti b/libs/common/pytz/zoneinfo/Pacific/Tahiti index 37e4e883368265e85b7db406348aa4b25350f100..481edd30580f00eccf69de4f1c332fc048210011 100644 GIT binary patch literal 151 zcmWHE%1kq2zzZ0GvP?kCF(n{$2jl<$#U~gTfMfy#i;r&zgRY?gLkJ0m{09P@265Q{ Kjk7Z}-~s@xxEtgE literal 173 zcmWHE%1kq2zyM4@5fBCe7@K2CKCJw2LcmK6!;jKm|0kvK#+l9t;1vsMvDpJ hsvy-Ey8Z(JNGFH}>wf5fMK>3hjkck&ovxuV7XU;j8IOAQJ<_T93&p zj22Km6U0@qngG=Fzs`((2Z#onbj?8=XedKHkdMVQ0gxO61DB1qp|PE=p)nT#ciJG| diff --git a/libs/common/pytz/zoneinfo/Pacific/Truk b/libs/common/pytz/zoneinfo/Pacific/Truk index e79bca2dafa7e914a9baa568edec1b428a2ac63d..7be2474dd91c8a7da181fcda09d838254b890d75 100644 GIT binary patch literal 172 zcmWHE%1kq2zzZ0GvP?kC(EucXoX#IWnxSU_1B;Ju2!pnv0YeC3LqG;F12OM^APCzQ n#Q~y=-Y%KI2$X@C*!h8h1#B)S$lL&5sNq~TKr8HY4Gp*eA?O^n literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|fkkpC;3@kprAq?7v5HmtZFb!zX X|2i{?Gaz&Do62PaG~Z6w(1;5FSBDv? diff --git a/libs/common/pytz/zoneinfo/Pacific/Wallis b/libs/common/pytz/zoneinfo/Pacific/Wallis index 8be9ac4d3bbe8f54267e85eebc8b11e1d612c9a4..47661d40a4188eb39e8d52e5af8ab23ef7f23766 100644 GIT binary patch literal 152 zcmWHE%1kq2zzZ0GvP?kC(EucXoJA%;nqkEO1{NRR5C&~SBZd$X4EYZPb!I|muo}c= M12oP~*U*Ry06;Jjs{jB1 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+mKkkpC;3@kprAq?7v5HmtZFb!zX X|2i{{6(Do)o62PaG~Z6w(1;5FY=ary diff --git a/libs/common/pytz/zoneinfo/Pacific/Yap b/libs/common/pytz/zoneinfo/Pacific/Yap index e79bca2dafa7e914a9baa568edec1b428a2ac63d..7be2474dd91c8a7da181fcda09d838254b890d75 100644 GIT binary patch literal 172 zcmWHE%1kq2zzZ0GvP?kC(EucXoX#IWnxSU_1B;Ju2!pnv0YeC3LqG;F12OM^APCzQ n#Q~y=-Y%KI2$X@C*!h8h1#B)S$lL&5sNq~TKr8HY4Gp*eA?O^n literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f8Vqu*u!2FYO>0~aJ a-pTV>ZcMIcUBB6kO@|q`>T8p8xMTo&A0Lqb delta 107 zcmca7(jhuQnQ_HL6;I}dl?fAba+$UM7EHVm!J>HU+K0^sj0+eUnI>Ogl4fL{{E$fu pNHQ}2WLz=%H*+sY{p4nryC4mdqgl5=ba1m7Fk|SrHra_w1^|0ujc&&hvTfKr%oRt9tHp*}_PBvtO@;3W2u4iQDat;pBwcr8(mXHS% delta 58 ucmey#+RHXUoRyVlp=Mq6`dN&cPwN7F+=6y$1XM diff --git a/libs/common/pytz/zoneinfo/ROK b/libs/common/pytz/zoneinfo/ROK index fa1cbd3952c362552f524061301b11f03770f335..96199e73e73aafacd89e48cb2855a96d7a134e1d 100644 GIT binary patch delta 283 zcmZo=dC4+C+>rqZ$O75yKrGOG{X@m1XJS7tIjeuTp4wk<{oUMz8$W&(+&ruM;MVo7 z54YFyKe+Sk(TBS|A0JG#@G@p$hC)VGBnTE`U?}GRSyA4>z{0>#H-Q1j>si3aGx-9e z(gblqkQ5Ant)KW;!3JdIe;@!^3!*_*gJ_WTAR6QZ5Djt$hz2(=S^dMr2rpG;MiwN<%nF1I4CNdk zJ>?w?EDQ{F6BsxqJ1{BnfP`RRg18`t>67Id6(-j+_7QKSFw8g*&E*{&qHDGF|)9;v9eDVXABg8$RS7!O(#6SYS1-G RBD6qQTsGPU7IwN8TmWj}N4=@BVvc%QZoFz- zT$wDNKQhv0-`q>Kcz-?33OAPVa5Bo?DyK$ew#2IQHsifJY}pnBw~uUd`_~e2>dV|| za_3Hni!eP9Pn1zA zqDqLWBBl7nrs8OEIR6|?oGIF|9LL|xo1NLM)!od^RxJfJArDzeXotwdB3cU%+@ptg5HzGvBnTm9pb$M+ z?LGsc4s!_EQ$%~xOXEfShdlpW!WHJ?DJ+%j(qVev0Z-liJ>GnV-{ryQGm|(>zH8E6 z<(s?f?4NukSdy!Kr+jVGVe9Lk`NqK#+Zy}f=U=qgzlwZsdw`X9CEqW-mB+sq_{rd+ zJRLUqpN39J+(lm-DM{V==+j5@VT^O+d6x{8kV_c@8B=iS)eegDFdCF99W z9{=9VDn51b%K1D?ysqQP$#GUSH0NE;-XqmL6JDxm-?`E{;MLG?PW4smftQ}1W$E&) zSHHX?uYE|df>ty#b9a1E)RTxzldezNj`tiw;w)PHn?~12ZjVSBNSb@I0sEnyM16UJNv_1}9 zSU{O@X_hKy;)tefiR{CP(v@l!2 ObfehX%E395Yy1bA+v^el diff --git a/libs/common/pytz/zoneinfo/UCT b/libs/common/pytz/zoneinfo/UCT index a88c4b665b3ec94711c735fc7593f460668958cd..91558be0c2bf903b2364215ba26d5227d6126508 100644 GIT binary patch literal 114 hcmWHE%1kq2AP5+NDp(+@LPMMxLdep^1=MQ51psH$25|rY literal 118 mcmWHE%1kq2zyORu5fFv}5SsW=+1sD+2%_Qx^~b delta 139 zcmeB>`5`?)T#%iCfdPa;;0+M7Z`9brJXw%Mm5-T;5e`{cfkN9SA7c@l{D{Snn}LB5 atN}=2Gh(v`YYIC7Gk|u2&6xa#R|Wu-P87-j diff --git a/libs/common/pytz/zoneinfo/US/East-Indiana b/libs/common/pytz/zoneinfo/US/East-Indiana index 09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4..a84b6e99671f7305d1c459956e16a4dd772fc60e 100644 GIT binary patch delta 321 zcmZqToy0pqT#$o-fdPa;AQ^}`Hfl^@j|6gy#!0`Y7 z&JBz#|NpOEz`!wCoynG$k%19xJDA4iqRrEpWSHscBvlkAak&PEn7X=z==d7z8R;45 N_!=7O8R{8u0RTxGC_n%J delta 305 zcmbQl+r&FTT#%iCfdPa;AQ_0+H)>2^~hM7K2QATnKmuql{sjEwfj<2zvk)DB$uc4uyp`HO3 E0D)T}6aWAK diff --git a/libs/common/pytz/zoneinfo/US/Eastern b/libs/common/pytz/zoneinfo/US/Eastern index 2f75480e069b60b6c58a9137c7eebd4796f74226..a8b9ab1992257d721ad627b14f535c3d4b020888 100644 GIT binary patch delta 139 zcmca0{XlwxxF8z?0|N+yz#AZD+o-XHnUQ(&L>>bs7S_pvEZZkn@rq6E;#KElWMBj; r0Fv0WZPsB;VaKXZ5w4HRH8{l7)g?s7*I3U;&p^l5&`{4%&wvX6q0|@_ delta 131 zcmaDLeL;GHxF9P70|N+yz#AZD-KepJd9omjArmtTklH@^7>n5CM=Xl$42&QpjM%hn mHepR+$D&6bs)x%pIK?G&X}aFJr#KLX=4yFq0-Ha!LaLn_U^_ delta 138 zcmeAX{v$L&T#%iCfdPa;U=|RwZ`8hb delta 38 tcmdlc_)lPhGGpgN6_t$<6PYHzWy)skoE*vQuz4r*Oco&5WAZ!>2>>384pjgE diff --git a/libs/common/pytz/zoneinfo/US/Mountain b/libs/common/pytz/zoneinfo/US/Mountain index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..abb2b974a47eb3e5c8b4f5d4370baf4898b239ab 100644 GIT binary patch delta 145 zcmeAXo+CU#T#$`{fdPa;U>*>&ZPch?;$~)Kf*>&Zq%q@;$~umLS~l98<{3fw&N6=9K)>0&cFy#$B0ee n=5@?pSa4~Shic^V4GuB)bqUe&HP$oIGtluhG}JTHGvERMYMd1i diff --git a/libs/common/pytz/zoneinfo/US/Pacific b/libs/common/pytz/zoneinfo/US/Pacific index 9dad4f4c75b373635ccbe634798f8d9e587e36c1..610e7af5fc13d9784de30d272c7c39d7938873a0 100644 GIT binary patch delta 134 zcmbOtwnS`#xF8z?0|N+yz%C$W+oI4c7POq4N!GmC84xq^d33=O#eb-e~$ delta 47 ocmZ3_xSMf;I4d&)0|WC!851@j4FqtJ92;JU2v=}$h@l}D0ICxPZU6uP diff --git a/libs/common/pytz/zoneinfo/UTC b/libs/common/pytz/zoneinfo/UTC index 5583f5b0c6e6949372648a7d75502e4d01b44931..91558be0c2bf903b2364215ba26d5227d6126508 100644 GIT binary patch literal 114 hcmWHE%1kq2AP5+NDp(+@LPMMxLdep^1=MQ51psH$25|rY literal 118 mcmWHE%1kq2zyORu5fFv}5Ssy;!5Z!w9KsOp8p6N`1=uui iKF<`)Vo!{2E}&5cATxD*jrC0R4D=0kd<_jiEG_^* delta 122 zcmey!cad*`xF8b)0|N+yKot-(ZPdtS5&<&!1Q=PuT|*ecU4ugy7=Zwr;?4J&f?4#j bspbOeH2~?=@io>n)icmH)bTYm0I|3L9z+sW diff --git a/libs/common/pytz/zoneinfo/Zulu b/libs/common/pytz/zoneinfo/Zulu index 5583f5b0c6e6949372648a7d75502e4d01b44931..91558be0c2bf903b2364215ba26d5227d6126508 100644 GIT binary patch literal 114 hcmWHE%1kq2AP5+NDp(+@LPMMxLdep^1=MQ51psH$25|rY literal 118 mcmWHE%1kq2zyORu5fFv}5Ss -# or -# or . +# or . +# The NIST file is used instead of its IERS upstream counterpart +# +# because under US law the NIST file is public domain +# whereas the IERS file's copyright and license status is unclear. # For more about leap-seconds.list, please see # The NTP Timescale and Leap Seconds # . -# The International Earth Rotation and Reference Systems Service +# The rules for leap seconds are specified in Annex 1 (Time scales) of: +# Standard-frequency and time-signal emissions. +# International Telecommunication Union - Radiocommunication Sector +# (ITU-R) Recommendation TF.460-6 (02/2002) +# . +# The International Earth Rotation and Reference Systems Service (IERS) # periodically uses leap seconds to keep UTC to within 0.9 s of UT1 -# (which measures the true angular orientation of the earth in space) +# (a proxy for Earth's angle in space as measured by astronomers) # and publishes leap second data in a copyrighted file # . # See: Levine J. Coordinated Universal Time and the leap second. # URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 # . -# There were no leap seconds before 1972, because the official mechanism -# accounting for the discrepancy between atomic time and the earth's rotation -# did not exist. -# The correction (+ or -) is made at the given time, so lines -# will typically look like: -# Leap YEAR MON DAY 23:59:60 + R/S -# or -# Leap YEAR MON DAY 23:59:59 - R/S +# There were no leap seconds before 1972, as no official mechanism +# accounted for the discrepancy between atomic time (TAI) and the earth's +# rotation. The first ("1 Jan 1972") data line in leap-seconds.list +# does not denote a leap second; it denotes the start of the current definition +# of UTC. -# If the leap second is Rolling (R) the given time is local time (unused here). +# All leap-seconds are Stationary (S) at the given UTC time. +# The correction (+ or -) is made at the given time, so in the unlikely +# event of a negative leap second, a line would look like this: +# Leap YEAR MON DAY 23:59:59 - S +# Typical lines look like this: +# Leap YEAR MON DAY 23:59:60 + S Leap 1972 Jun 30 23:59:60 + S Leap 1972 Dec 31 23:59:60 + S Leap 1973 Dec 31 23:59:60 + S @@ -58,9 +68,15 @@ Leap 2012 Jun 30 23:59:60 + S Leap 2015 Jun 30 23:59:60 + S Leap 2016 Dec 31 23:59:60 + S -# POSIX timestamps for the data in this file: -#updated 1467936000 -#expires 1561680000 +# UTC timestamp when this leap second list expires. +# Any additional leap seconds will come after this. +# This Expires line is commented out for now, +# so that pre-2020a zic implementations do not reject this file. +#Expires 2023 Jun 28 00:00:00 -# Updated through IERS Bulletin C56 -# File expires on: 28 June 2019 +# POSIX timestamps for the data in this file: +#updated 1467936000 (2016-07-08 00:00:00 UTC) +#expires 1687910400 (2023-06-28 00:00:00 UTC) + +# Updated through IERS Bulletin C64 +# File expires on: 28 June 2023 diff --git a/libs/common/pytz/zoneinfo/posixrules b/libs/common/pytz/zoneinfo/posixrules deleted file mode 100644 index 2f75480e069b60b6c58a9137c7eebd4796f74226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 zcmeI!Sx}W_9LMpa;)WucQm$lLAu8a83sO>PgnGmjT+r~zKnAt=mx@@5l_ud#S(Afp zgQ+NJDQ=jk;TkzM<%0WykET>6`Y0}>c}~ywz3nD0y6a`$^Et!dc=!AM;}TLQ^>F>; zscV13%X8Jfd~fl#{m5MvC`-5fp}tz+l4YO&q?RXNloj)S*LjoI$;$A2wQA%6lOK?+ z3VMEH3Opnbtdl%(plWh2bG+#$MfQ!leVGemFr@17u536ZLKXyRx;OQN?XeNpZyywcY2o*Ygn>_($mdA&IiTdbB#=7bOQy_ESH;Z{$eFTXIC* z*JU#8--7(j?+@S9SL)p`SMD6ue^iv2 ztH-zK%F-fpZD*OfUU)>z(js+Z(Pp_hcZsS>%aL0XW~tk;8FFX9ICVEHL8?2=)PMR% z%Do0-^}Xsb=KgQ}^yv}bEu!YeeWlHXO4au8RcW{TpbFgZvpl+N zgKD4dGLOCUiRuu4(R7?#s2>mCXPy}Rv3@dOl?m!RO$T}QO0aLd4lZ9Qov-xKT}rZ~ zYgwEM$xW5eO}$lE<`C)jNlVo|CB^i3rcvex`4m)4FfP zb<^+u4joZ?*z`Y>t0N1q$y3|k)=w`wBm=&fsH4(0$}{uls%K*t%X3LDtASzZGHBp) zYEV^yi4K{dqstbW7{6z9%%-VkaAik521wxg=IP|-eY7@k$yc~n>W&y=xG6a%=FkE*j6qh*H5C|M!1 zsuR?kx$ntaCnMGD%oLfkHBem`HU8;7i8vfMrso_7U>3{Iw{k_+_E!XApdVkne z%g5_2Uhit)d~fW0HXZ7Ya}643-;wqmZQtQ>cFSC@TFysY4K~ngpTs)mBV-GaJw!GU z*+pa4 zZ;{PKb{E-RN4vks20PjvMz$E)V`P(&T}HMU*=J;8OBau!btwef>G!yA2 z(oRR)Po$xawxdW(k)9$=MY@W#73nL|SfsN^Ymwd}%|*J4v=`|w(qKp1VWh=KkC7%L zT}IlB^ciV1(rKjCNUxD*Bi%;Y?P&XrG~Cg49BH|u?K#qPr0YoAk-j61M>>zR9_c;O ze5CtG`yFlnksH9#-T}xh;Armwa!WYcdjh#B9PM3!+!n}vf!r9#oq^mM$i0Ew9LU{)+#bmNf!rXD_6|XA5l4HE zAUBDly-SeW1i4R;8wI&jkXr@0SMdLv<=@{dzV?&}wR diff --git a/libs/common/pytz/zoneinfo/tzdata.zi b/libs/common/pytz/zoneinfo/tzdata.zi index 7eb8193b..d8c78d47 100644 --- a/libs/common/pytz/zoneinfo/tzdata.zi +++ b/libs/common/pytz/zoneinfo/tzdata.zi @@ -1,7 +1,7 @@ -# version unknown +# version unknown-dirty # This zic input file is in the public domain. R d 1916 o - Jun 14 23s 1 S -R d 1916 1919 - O Sun>=1 23s 0 - +R d 1916 1919 - O Su>=1 23s 0 - R d 1917 o - Mar 24 23s 1 S R d 1918 o - Mar 9 23s 1 S R d 1919 o - Mar 1 23s 1 S @@ -22,7 +22,7 @@ R d 1978 o - Mar 24 1 1 S R d 1978 o - S 22 3 0 - R d 1980 o - Ap 25 0 1 S R d 1980 o - O 31 2 0 - -Z Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:1 +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 16 0:9:21 - PMT 1911 Mar 11 0 d WE%sT 1940 F 25 2 1 d CE%sT 1946 O 7 @@ -43,15 +43,6 @@ Z Africa/Ndjamena 1:0:12 - LMT 1912 1 - WAT Z Africa/Abidjan -0:16:8 - LMT 1912 0 - GMT -Li Africa/Abidjan Africa/Bamako -Li Africa/Abidjan Africa/Banjul -Li Africa/Abidjan Africa/Conakry -Li Africa/Abidjan Africa/Dakar -Li Africa/Abidjan Africa/Freetown -Li Africa/Abidjan Africa/Lome -Li Africa/Abidjan Africa/Nouakchott -Li Africa/Abidjan Africa/Ouagadougou -Li Africa/Abidjan Atlantic/St_Helena R K 1940 o - Jul 15 0 1 S R K 1940 o - O 1 0 0 - R K 1941 o - Ap 15 0 1 S @@ -86,27 +77,15 @@ R K 2014 o - Jul 31 24 1 S R K 2014 o - S lastTh 24 0 - Z Africa/Cairo 2:5:9 - LMT 1900 O 2 K EE%sT -R GH 1920 1942 - S 1 0 0:20 - -R GH 1920 1942 - D 31 0 0 - -Z Africa/Accra -0:0:52 - LMT 1918 -0 GH GMT/+0020 Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u -1 - -01 1975 0 - GMT -Z Africa/Nairobi 2:27:16 - LMT 1928 Jul -3 - EAT 1930 -2:30 - +0230 1940 -2:45 - +0245 1960 +Z Africa/Nairobi 2:27:16 - LMT 1908 May +2:30 - +0230 1928 Jun 30 24 +3 - EAT 1930 Ja 4 24 +2:30 - +0230 1936 D 31 24 +2:45 - +0245 1942 Jul 31 24 3 - EAT -Li Africa/Nairobi Africa/Addis_Ababa -Li Africa/Nairobi Africa/Asmara -Li Africa/Nairobi Africa/Dar_es_Salaam -Li Africa/Nairobi Africa/Djibouti -Li Africa/Nairobi Africa/Kampala -Li Africa/Nairobi Africa/Mogadishu -Li Africa/Nairobi Indian/Antananarivo -Li Africa/Nairobi Indian/Comoro -Li Africa/Nairobi Indian/Mayotte Z Africa/Monrovia -0:43:8 - LMT 1882 -0:43:8 - MMT 1919 Mar -0:44:30 - MMT 1972 Ja 7 @@ -139,8 +118,8 @@ Z Africa/Tripoli 0:52:44 - LMT 1920 2 - EET R MU 1982 o - O 10 0 1 - R MU 1983 o - Mar 21 0 0 - -R MU 2008 o - O lastSun 2 1 - -R MU 2009 o - Mar lastSun 2 0 - +R MU 2008 o - O lastSu 2 1 - +R MU 2009 o - Mar lastSu 2 0 - Z Indian/Mauritius 3:50 - LMT 1907 4 MU +04/+05 R M 1939 o - S 12 0 1 - @@ -166,14 +145,14 @@ R M 2010 o - May 2 0 1 - R M 2010 o - Au 8 0 0 - R M 2011 o - Ap 3 0 1 - R M 2011 o - Jul 31 0 0 - -R M 2012 2013 - Ap lastSun 2 1 - +R M 2012 2013 - Ap lastSu 2 1 - R M 2012 o - Jul 20 3 0 - R M 2012 o - Au 20 2 1 - R M 2012 o - S 30 3 0 - R M 2013 o - Jul 7 3 0 - R M 2013 o - Au 10 2 1 - -R M 2013 2018 - O lastSun 3 0 - -R M 2014 2018 - Mar lastSun 2 1 - +R M 2013 2018 - O lastSu 3 0 - +R M 2014 2018 - Mar lastSu 2 1 - R M 2014 o - Jun 28 3 0 - R M 2014 o - Au 2 2 1 - R M 2015 o - Jun 14 3 0 - @@ -184,70 +163,195 @@ R M 2017 o - May 21 3 0 - R M 2017 o - Jul 2 2 1 - R M 2018 o - May 13 3 0 - R M 2018 o - Jun 17 2 1 - +R M 2019 o - May 5 3 -1 - +R M 2019 o - Jun 9 2 0 - +R M 2020 o - Ap 19 3 -1 - +R M 2020 o - May 31 2 0 - +R M 2021 o - Ap 11 3 -1 - +R M 2021 o - May 16 2 0 - +R M 2022 o - Mar 27 3 -1 - +R M 2022 o - May 8 2 0 - +R M 2023 o - Mar 19 3 -1 - +R M 2023 o - Ap 30 2 0 - +R M 2024 o - Mar 10 3 -1 - +R M 2024 o - Ap 14 2 0 - +R M 2025 o - F 23 3 -1 - +R M 2025 o - Ap 6 2 0 - +R M 2026 o - F 15 3 -1 - +R M 2026 o - Mar 22 2 0 - +R M 2027 o - F 7 3 -1 - +R M 2027 o - Mar 14 2 0 - +R M 2028 o - Ja 23 3 -1 - +R M 2028 o - Mar 5 2 0 - +R M 2029 o - Ja 14 3 -1 - +R M 2029 o - F 18 2 0 - +R M 2029 o - D 30 3 -1 - +R M 2030 o - F 10 2 0 - +R M 2030 o - D 22 3 -1 - +R M 2031 o - F 2 2 0 - +R M 2031 o - D 14 3 -1 - +R M 2032 o - Ja 18 2 0 - +R M 2032 o - N 28 3 -1 - +R M 2033 o - Ja 9 2 0 - +R M 2033 o - N 20 3 -1 - +R M 2033 o - D 25 2 0 - +R M 2034 o - N 5 3 -1 - +R M 2034 o - D 17 2 0 - +R M 2035 o - O 28 3 -1 - +R M 2035 o - D 9 2 0 - +R M 2036 o - O 19 3 -1 - +R M 2036 o - N 23 2 0 - +R M 2037 o - O 4 3 -1 - +R M 2037 o - N 15 2 0 - +R M 2038 o - S 26 3 -1 - +R M 2038 o - N 7 2 0 - +R M 2039 o - S 18 3 -1 - +R M 2039 o - O 23 2 0 - +R M 2040 o - S 2 3 -1 - +R M 2040 o - O 14 2 0 - +R M 2041 o - Au 25 3 -1 - +R M 2041 o - S 29 2 0 - +R M 2042 o - Au 10 3 -1 - +R M 2042 o - S 21 2 0 - +R M 2043 o - Au 2 3 -1 - +R M 2043 o - S 13 2 0 - +R M 2044 o - Jul 24 3 -1 - +R M 2044 o - Au 28 2 0 - +R M 2045 o - Jul 9 3 -1 - +R M 2045 o - Au 20 2 0 - +R M 2046 o - Jul 1 3 -1 - +R M 2046 o - Au 12 2 0 - +R M 2047 o - Jun 23 3 -1 - +R M 2047 o - Jul 28 2 0 - +R M 2048 o - Jun 7 3 -1 - +R M 2048 o - Jul 19 2 0 - +R M 2049 o - May 30 3 -1 - +R M 2049 o - Jul 4 2 0 - +R M 2050 o - May 15 3 -1 - +R M 2050 o - Jun 26 2 0 - +R M 2051 o - May 7 3 -1 - +R M 2051 o - Jun 18 2 0 - +R M 2052 o - Ap 28 3 -1 - +R M 2052 o - Jun 2 2 0 - +R M 2053 o - Ap 13 3 -1 - +R M 2053 o - May 25 2 0 - +R M 2054 o - Ap 5 3 -1 - +R M 2054 o - May 17 2 0 - +R M 2055 o - Mar 28 3 -1 - +R M 2055 o - May 2 2 0 - +R M 2056 o - Mar 12 3 -1 - +R M 2056 o - Ap 23 2 0 - +R M 2057 o - Mar 4 3 -1 - +R M 2057 o - Ap 8 2 0 - +R M 2058 o - F 17 3 -1 - +R M 2058 o - Mar 31 2 0 - +R M 2059 o - F 9 3 -1 - +R M 2059 o - Mar 23 2 0 - +R M 2060 o - F 1 3 -1 - +R M 2060 o - Mar 7 2 0 - +R M 2061 o - Ja 16 3 -1 - +R M 2061 o - F 27 2 0 - +R M 2062 o - Ja 8 3 -1 - +R M 2062 o - F 19 2 0 - +R M 2062 o - D 31 3 -1 - +R M 2063 o - F 4 2 0 - +R M 2063 o - D 16 3 -1 - +R M 2064 o - Ja 27 2 0 - +R M 2064 o - D 7 3 -1 - +R M 2065 o - Ja 11 2 0 - +R M 2065 o - N 22 3 -1 - +R M 2066 o - Ja 3 2 0 - +R M 2066 o - N 14 3 -1 - +R M 2066 o - D 26 2 0 - +R M 2067 o - N 6 3 -1 - +R M 2067 o - D 11 2 0 - +R M 2068 o - O 21 3 -1 - +R M 2068 o - D 2 2 0 - +R M 2069 o - O 13 3 -1 - +R M 2069 o - N 24 2 0 - +R M 2070 o - O 5 3 -1 - +R M 2070 o - N 9 2 0 - +R M 2071 o - S 20 3 -1 - +R M 2071 o - N 1 2 0 - +R M 2072 o - S 11 3 -1 - +R M 2072 o - O 16 2 0 - +R M 2073 o - Au 27 3 -1 - +R M 2073 o - O 8 2 0 - +R M 2074 o - Au 19 3 -1 - +R M 2074 o - S 30 2 0 - +R M 2075 o - Au 11 3 -1 - +R M 2075 o - S 15 2 0 - +R M 2076 o - Jul 26 3 -1 - +R M 2076 o - S 6 2 0 - +R M 2077 o - Jul 18 3 -1 - +R M 2077 o - Au 29 2 0 - +R M 2078 o - Jul 10 3 -1 - +R M 2078 o - Au 14 2 0 - +R M 2079 o - Jun 25 3 -1 - +R M 2079 o - Au 6 2 0 - +R M 2080 o - Jun 16 3 -1 - +R M 2080 o - Jul 21 2 0 - +R M 2081 o - Jun 1 3 -1 - +R M 2081 o - Jul 13 2 0 - +R M 2082 o - May 24 3 -1 - +R M 2082 o - Jul 5 2 0 - +R M 2083 o - May 16 3 -1 - +R M 2083 o - Jun 20 2 0 - +R M 2084 o - Ap 30 3 -1 - +R M 2084 o - Jun 11 2 0 - +R M 2085 o - Ap 22 3 -1 - +R M 2085 o - Jun 3 2 0 - +R M 2086 o - Ap 14 3 -1 - +R M 2086 o - May 19 2 0 - +R M 2087 o - Mar 30 3 -1 - +R M 2087 o - May 11 2 0 - Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 0 M +00/+01 1984 Mar 16 1 - +01 1986 -0 M +00/+01 2018 O 27 -1 - +01 +0 M +00/+01 2018 O 28 3 +1 M +01/+00 Z Africa/El_Aaiun -0:52:48 - LMT 1934 -1 - -01 1976 Ap 14 -0 M +00/+01 2018 O 27 -1 - +01 +0 M +00/+01 2018 O 28 3 +1 M +01/+00 Z Africa/Maputo 2:10:20 - LMT 1903 Mar 2 - CAT -Li Africa/Maputo Africa/Blantyre -Li Africa/Maputo Africa/Bujumbura -Li Africa/Maputo Africa/Gaborone -Li Africa/Maputo Africa/Harare -Li Africa/Maputo Africa/Kigali -Li Africa/Maputo Africa/Lubumbashi -Li Africa/Maputo Africa/Lusaka R NA 1994 o - Mar 21 0 -1 WAT -R NA 1994 2017 - S Sun>=1 2 0 CAT -R NA 1995 2017 - Ap Sun>=1 2 -1 WAT +R NA 1994 2017 - S Su>=1 2 0 CAT +R NA 1995 2017 - Ap Su>=1 2 -1 WAT Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 1:30 - +0130 1903 Mar 2 - SAST 1942 S 20 2 2 1 SAST 1943 Mar 21 2 2 - SAST 1990 Mar 21 2 NA %s -Z Africa/Lagos 0:13:36 - LMT 1919 S +Z Africa/Lagos 0:13:35 - LMT 1905 Jul +0 - GMT 1908 Jul +0:13:35 - LMT 1914 +0:30 - +0030 1919 S 1 - WAT -Li Africa/Lagos Africa/Bangui -Li Africa/Lagos Africa/Brazzaville -Li Africa/Lagos Africa/Douala -Li Africa/Lagos Africa/Kinshasa -Li Africa/Lagos Africa/Libreville -Li Africa/Lagos Africa/Luanda -Li Africa/Lagos Africa/Malabo -Li Africa/Lagos Africa/Niamey -Li Africa/Lagos Africa/Porto-Novo -Z Indian/Reunion 3:41:52 - LMT 1911 Jun -4 - +04 Z Africa/Sao_Tome 0:26:56 - LMT 1884 -0:36:45 - LMT 1912 Ja 1 0u 0 - GMT 2018 Ja 1 1 -1 - WAT -Z Indian/Mahe 3:41:48 - LMT 1906 Jun -4 - +04 -R SA 1942 1943 - S Sun>=15 2 1 - -R SA 1943 1944 - Mar Sun>=15 2 0 - +1 - WAT 2019 Ja 1 2 +0 - GMT +R SA 1942 1943 - S Su>=15 2 1 - +R SA 1943 1944 - Mar Su>=15 2 0 - Z Africa/Johannesburg 1:52 - LMT 1892 F 8 1:30 - SAST 1903 Mar 2 SA SAST -Li Africa/Johannesburg Africa/Maseru -Li Africa/Johannesburg Africa/Mbabane R SD 1970 o - May 1 0 1 S R SD 1970 1985 - O 15 0 0 - R SD 1971 o - Ap 30 0 1 S -R SD 1972 1985 - Ap lastSun 0 1 S +R SD 1972 1985 - Ap lastSu 0 1 S Z Africa/Khartoum 2:10:8 - LMT 1931 2 SD CA%sT 2000 Ja 15 12 3 - EAT 2017 N 2 - CAT Z Africa/Juba 2:6:28 - LMT 1931 2 SD CA%sT 2000 Ja 15 12 -3 - EAT +3 - EAT 2021 F +2 - CAT R n 1939 o - Ap 15 23s 1 S R n 1939 o - N 18 23s 0 - R n 1940 o - F 25 23s 1 S @@ -266,13 +370,13 @@ R n 1977 o - S 24 0s 0 - R n 1978 o - May 1 0s 1 S R n 1978 o - O 1 0s 0 - R n 1988 o - Jun 1 0s 1 S -R n 1988 1990 - S lastSun 0s 0 - +R n 1988 1990 - S lastSu 0s 0 - R n 1989 o - Mar 26 0s 1 S R n 1990 o - May 1 0s 1 S R n 2005 o - May 1 0s 1 S R n 2005 o - S 30 1s 0 - -R n 2006 2008 - Mar lastSun 2s 1 S -R n 2006 2008 - O lastSun 2s 0 - +R n 2006 2008 - Mar lastSu 2s 1 S +R n 2006 2008 - O lastSu 2s 0 - Z Africa/Tunis 0:40:44 - LMT 1881 May 12 0:9:21 - PMT 1911 Mar 11 1 n CE%sT @@ -283,7 +387,12 @@ Z Antarctica/Casey 0 - -00 1969 11 - +11 2012 F 21 17u 8 - +08 2016 O 22 11 - +11 2018 Mar 11 4 -8 - +08 +8 - +08 2018 O 7 4 +11 - +11 2019 Mar 17 3 +8 - +08 2019 O 4 3 +11 - +11 2020 Mar 8 3 +8 - +08 2020 O 4 0:1 +11 - +11 Z Antarctica/Davis 0 - -00 1957 Ja 13 7 - +07 1964 N 0 - -00 1969 F @@ -295,27 +404,17 @@ Z Antarctica/Davis 0 - -00 1957 Ja 13 Z Antarctica/Mawson 0 - -00 1954 F 13 6 - +06 2009 O 18 2 5 - +05 -Z Indian/Kerguelen 0 - -00 1950 -5 - +05 -Z Antarctica/DumontDUrville 0 - -00 1947 -10 - +10 1952 Ja 14 -0 - -00 1956 N -10 - +10 -Z Antarctica/Syowa 0 - -00 1957 Ja 29 -3 - +03 -R Tr 2005 ma - Mar lastSun 1u 2 +02 -R Tr 2004 ma - O lastSun 1u 0 +00 +R Tr 2005 ma - Mar lastSu 1u 2 +02 +R Tr 2004 ma - O lastSu 1u 0 +00 Z Antarctica/Troll 0 - -00 2005 F 12 0 Tr %s -Z Antarctica/Vostok 0 - -00 1957 D 16 -6 - +06 Z Antarctica/Rothera 0 - -00 1976 D -3 - -03 Z Asia/Kabul 4:36:48 - LMT 1890 4 - +04 1945 4:30 - +0430 -R AM 2011 o - Mar lastSun 2s 1 - -R AM 2011 o - O lastSun 2s 0 - +R AM 2011 o - Mar lastSu 2s 1 - +R AM 2011 o - O lastSu 2s 0 - Z Asia/Yerevan 2:58 - LMT 1924 May 2 3 - +03 1957 Mar 4 R +04/+05 1991 Mar 31 2s @@ -323,12 +422,12 @@ Z Asia/Yerevan 2:58 - LMT 1924 May 2 4 - +04 1997 4 R +04/+05 2011 4 AM +04/+05 -R AZ 1997 2015 - Mar lastSun 4 1 - -R AZ 1997 2015 - O lastSun 5 0 - +R AZ 1997 2015 - Mar lastSu 4 1 - +R AZ 1997 2015 - O lastSu 5 0 - Z Asia/Baku 3:19:24 - LMT 1924 May 2 3 - +03 1957 Mar 4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1992 S lastSun 2s +3 R +03/+04 1992 S lastSu 2s 4 - +04 1996 4 E +04/+05 1997 4 AZ +04/+05 @@ -347,14 +446,13 @@ Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 Z Indian/Chagos 4:49:40 - LMT 1907 5 - +05 1996 6 - +06 -Z Asia/Brunei 7:39:40 - LMT 1926 Mar -7:30 - +0730 1933 -8 - +08 Z Asia/Yangon 6:24:47 - LMT 1880 6:24:47 - RMT 1920 6:30 - +0630 1942 May 9 - +09 1945 May 3 6:30 - +0630 +R Sh 1919 o - Ap 12 24 1 D +R Sh 1919 o - S 30 24 0 S R Sh 1940 o - Jun 1 0 1 D R Sh 1940 o - O 12 24 0 S R Sh 1941 o - Mar 15 0 1 D @@ -368,35 +466,32 @@ R Sh 1947 o - O 31 24 0 S R Sh 1948 1949 - May 1 0 1 D R Sh 1948 1949 - S 30 24 0 S R CN 1986 o - May 4 2 1 D -R CN 1986 1991 - S Sun>=11 2 0 S -R CN 1987 1991 - Ap Sun>=11 2 1 D +R CN 1986 1991 - S Su>=11 2 0 S +R CN 1987 1991 - Ap Su>=11 2 1 D Z Asia/Shanghai 8:5:43 - LMT 1901 8 Sh C%sT 1949 May 28 8 CN C%sT Z Asia/Urumqi 5:50:20 - LMT 1928 6 - +06 -R HK 1941 o - Ap 1 3:30 1 S -R HK 1941 o - S 30 3:30 0 - -R HK 1946 o - Ap 20 3:30 1 S -R HK 1946 o - D 1 3:30 0 - -R HK 1947 o - Ap 13 3:30 1 S -R HK 1947 o - D 30 3:30 0 - -R HK 1948 o - May 2 3:30 1 S -R HK 1948 1951 - O lastSun 3:30 0 - -R HK 1952 o - O 25 3:30 0 - -R HK 1949 1953 - Ap Sun>=1 3:30 1 S -R HK 1953 o - N 1 3:30 0 - -R HK 1954 1964 - Mar Sun>=18 3:30 1 S -R HK 1954 o - O 31 3:30 0 - -R HK 1955 1964 - N Sun>=1 3:30 0 - -R HK 1965 1976 - Ap Sun>=16 3:30 1 S -R HK 1965 1976 - O Sun>=16 3:30 0 - +R HK 1946 o - Ap 21 0 1 S +R HK 1946 o - D 1 3:30s 0 - +R HK 1947 o - Ap 13 3:30s 1 S +R HK 1947 o - N 30 3:30s 0 - +R HK 1948 o - May 2 3:30s 1 S +R HK 1948 1952 - O Su>=28 3:30s 0 - +R HK 1949 1953 - Ap Su>=1 3:30 1 S +R HK 1953 1964 - O Su>=31 3:30 0 - +R HK 1954 1964 - Mar Su>=18 3:30 1 S +R HK 1965 1976 - Ap Su>=16 3:30 1 S +R HK 1965 1976 - O Su>=16 3:30 0 - R HK 1973 o - D 30 3:30 1 S -R HK 1979 o - May Sun>=8 3:30 1 S -R HK 1979 o - O Sun>=16 3:30 0 - -Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 -8 HK HK%sT 1941 D 25 -9 - JST 1945 S 15 +R HK 1979 o - May 13 3:30 1 S +R HK 1979 o - O 21 3:30 0 - +Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 29 17u +8 - HKT 1941 Jun 15 3 +8 1 HKST 1941 O 1 4 +8 0:30 HKWT 1941 D 25 +9 - JST 1945 N 18 2 8 HK HK%sT R f 1946 o - May 15 0 1 D R f 1946 o - O 1 0 0 S @@ -426,24 +521,24 @@ R _ 1947 o - Ap 19 23s 1 D R _ 1947 o - N 30 23s 0 S R _ 1948 o - May 2 23s 1 D R _ 1948 o - O 31 23s 0 S -R _ 1949 1950 - Ap Sat>=1 23s 1 D -R _ 1949 1950 - O lastSat 23s 0 S +R _ 1949 1950 - Ap Sa>=1 23s 1 D +R _ 1949 1950 - O lastSa 23s 0 S R _ 1951 o - Mar 31 23s 1 D R _ 1951 o - O 28 23s 0 S -R _ 1952 1953 - Ap Sat>=1 23s 1 D +R _ 1952 1953 - Ap Sa>=1 23s 1 D R _ 1952 o - N 1 23s 0 S -R _ 1953 1954 - O lastSat 23s 0 S -R _ 1954 1956 - Mar Sat>=17 23s 1 D +R _ 1953 1954 - O lastSa 23s 0 S +R _ 1954 1956 - Mar Sa>=17 23s 1 D R _ 1955 o - N 5 23s 0 S -R _ 1956 1964 - N Sun>=1 3:30 0 S -R _ 1957 1964 - Mar Sun>=18 3:30 1 D -R _ 1965 1973 - Ap Sun>=16 3:30 1 D -R _ 1965 1966 - O Sun>=16 2:30 0 S -R _ 1967 1976 - O Sun>=16 3:30 0 S +R _ 1956 1964 - N Su>=1 3:30 0 S +R _ 1957 1964 - Mar Su>=18 3:30 1 D +R _ 1965 1973 - Ap Su>=16 3:30 1 D +R _ 1965 1966 - O Su>=16 2:30 0 S +R _ 1967 1976 - O Su>=16 3:30 0 S R _ 1973 o - D 30 3:30 1 D -R _ 1975 1976 - Ap Sun>=16 3:30 1 D +R _ 1975 1976 - Ap Su>=16 3:30 1 D R _ 1979 o - May 13 3:30 1 D -R _ 1979 o - O Sun>=16 3:30 0 S +R _ 1979 o - O Su>=16 3:30 0 S Z Asia/Macau 7:34:10 - LMT 1904 O 30 8 - CST 1941 D 21 23 9 _ +09/+10 1945 S 30 24 @@ -452,11 +547,11 @@ R CY 1975 o - Ap 13 0 1 S R CY 1975 o - O 12 0 0 - R CY 1976 o - May 15 0 1 S R CY 1976 o - O 11 0 0 - -R CY 1977 1980 - Ap Sun>=1 0 1 S +R CY 1977 1980 - Ap Su>=1 0 1 S R CY 1977 o - S 25 0 0 - R CY 1978 o - O 2 0 0 - -R CY 1979 1997 - S lastSun 0 0 - -R CY 1981 1998 - Mar lastSun 0 1 S +R CY 1979 1997 - S lastSu 0 0 - +R CY 1981 1998 - Mar lastSu 0 1 S Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 2 CY EE%sT 1998 S 2 E EE%sT @@ -465,17 +560,16 @@ Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 2 E EE%sT 2016 S 8 3 - +03 2017 O 29 1u 2 E EE%sT -Li Asia/Nicosia Europe/Nicosia Z Asia/Tbilisi 2:59:11 - LMT 1880 2:59:11 - TBMT 1924 May 2 3 - +03 1957 Mar 4 R +04/+05 1991 Mar 31 2s 3 R +03/+04 1992 -3 e +03/+04 1994 S lastSun -4 e +04/+05 1996 O lastSun -4 1 +05 1997 Mar lastSun +3 e +03/+04 1994 S lastSu +4 e +04/+05 1996 O lastSu +4 1 +05 1997 Mar lastSu 4 e +04/+05 2004 Jun 27 -3 R +03/+04 2005 Mar lastSun 2 +3 R +03/+04 2005 Mar lastSu 2 4 - +04 Z Asia/Dili 8:22:20 - LMT 1912 8 - +08 1942 F 21 23 @@ -491,7 +585,7 @@ Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 5:30 1 +0630 1945 O 15 5:30 - IST Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 -7:7:12 - BMT 1923 D 31 23:47:12 +7:7:12 - BMT 1923 D 31 16:40u 7:20 - +0720 1932 N 7:30 - +0730 1942 Mar 23 9 - +09 1945 S 23 @@ -517,130 +611,123 @@ Z Asia/Jayapura 9:22:48 - LMT 1932 N 9 - +09 1944 S 9:30 - +0930 1964 9 - WIT -R i 1978 1980 - Mar 21 0 1 - -R i 1978 o - O 21 0 0 - -R i 1979 o - S 19 0 0 - -R i 1980 o - S 23 0 0 - -R i 1991 o - May 3 0 1 - -R i 1992 1995 - Mar 22 0 1 - -R i 1991 1995 - S 22 0 0 - -R i 1996 o - Mar 21 0 1 - -R i 1996 o - S 21 0 0 - -R i 1997 1999 - Mar 22 0 1 - -R i 1997 1999 - S 22 0 0 - -R i 2000 o - Mar 21 0 1 - -R i 2000 o - S 21 0 0 - -R i 2001 2003 - Mar 22 0 1 - -R i 2001 2003 - S 22 0 0 - -R i 2004 o - Mar 21 0 1 - -R i 2004 o - S 21 0 0 - -R i 2005 o - Mar 22 0 1 - -R i 2005 o - S 22 0 0 - -R i 2008 o - Mar 21 0 1 - -R i 2008 o - S 21 0 0 - -R i 2009 2011 - Mar 22 0 1 - -R i 2009 2011 - S 22 0 0 - -R i 2012 o - Mar 21 0 1 - -R i 2012 o - S 21 0 0 - -R i 2013 2015 - Mar 22 0 1 - -R i 2013 2015 - S 22 0 0 - -R i 2016 o - Mar 21 0 1 - -R i 2016 o - S 21 0 0 - -R i 2017 2019 - Mar 22 0 1 - -R i 2017 2019 - S 22 0 0 - -R i 2020 o - Mar 21 0 1 - -R i 2020 o - S 21 0 0 - -R i 2021 2023 - Mar 22 0 1 - -R i 2021 2023 - S 22 0 0 - -R i 2024 o - Mar 21 0 1 - -R i 2024 o - S 21 0 0 - -R i 2025 2027 - Mar 22 0 1 - -R i 2025 2027 - S 22 0 0 - -R i 2028 2029 - Mar 21 0 1 - -R i 2028 2029 - S 21 0 0 - -R i 2030 2031 - Mar 22 0 1 - -R i 2030 2031 - S 22 0 0 - -R i 2032 2033 - Mar 21 0 1 - -R i 2032 2033 - S 21 0 0 - -R i 2034 2035 - Mar 22 0 1 - -R i 2034 2035 - S 22 0 0 - -R i 2036 ma - Mar 21 0 1 - -R i 2036 ma - S 21 0 0 - +R i 1910 o - Ja 1 0 0 - +R i 1977 o - Mar 21 23 1 - +R i 1977 o - O 20 24 0 - +R i 1978 o - Mar 24 24 1 - +R i 1978 o - Au 5 1 0 - +R i 1979 o - May 26 24 1 - +R i 1979 o - S 18 24 0 - +R i 1980 o - Mar 20 24 1 - +R i 1980 o - S 22 24 0 - +R i 1991 o - May 2 24 1 - +R i 1992 1995 - Mar 21 24 1 - +R i 1991 1995 - S 21 24 0 - +R i 1996 o - Mar 20 24 1 - +R i 1996 o - S 20 24 0 - +R i 1997 1999 - Mar 21 24 1 - +R i 1997 1999 - S 21 24 0 - +R i 2000 o - Mar 20 24 1 - +R i 2000 o - S 20 24 0 - +R i 2001 2003 - Mar 21 24 1 - +R i 2001 2003 - S 21 24 0 - +R i 2004 o - Mar 20 24 1 - +R i 2004 o - S 20 24 0 - +R i 2005 o - Mar 21 24 1 - +R i 2005 o - S 21 24 0 - +R i 2008 o - Mar 20 24 1 - +R i 2008 o - S 20 24 0 - +R i 2009 2011 - Mar 21 24 1 - +R i 2009 2011 - S 21 24 0 - +R i 2012 o - Mar 20 24 1 - +R i 2012 o - S 20 24 0 - +R i 2013 2015 - Mar 21 24 1 - +R i 2013 2015 - S 21 24 0 - +R i 2016 o - Mar 20 24 1 - +R i 2016 o - S 20 24 0 - +R i 2017 2019 - Mar 21 24 1 - +R i 2017 2019 - S 21 24 0 - +R i 2020 o - Mar 20 24 1 - +R i 2020 o - S 20 24 0 - +R i 2021 2022 - Mar 21 24 1 - +R i 2021 2022 - S 21 24 0 - Z Asia/Tehran 3:25:44 - LMT 1916 -3:25:44 - TMT 1946 -3:30 - +0330 1977 N +3:25:44 - TMT 1935 Jun 13 +3:30 i +0330/+0430 1977 O 20 24 4 i +04/+05 1979 3:30 i +0330/+0430 R IQ 1982 o - May 1 0 1 - R IQ 1982 1984 - O 1 0 0 - R IQ 1983 o - Mar 31 0 1 - R IQ 1984 1985 - Ap 1 0 1 - -R IQ 1985 1990 - S lastSun 1s 0 - -R IQ 1986 1990 - Mar lastSun 1s 1 - +R IQ 1985 1990 - S lastSu 1s 0 - +R IQ 1986 1990 - Mar lastSu 1s 1 - R IQ 1991 2007 - Ap 1 3s 1 - R IQ 1991 2007 - O 1 3s 0 - Z Asia/Baghdad 2:57:40 - LMT 1890 2:57:36 - BMT 1918 3 - +03 1982 May 3 IQ +03/+04 -R Z 1940 o - Jun 1 0 1 D -R Z 1942 1944 - N 1 0 0 S -R Z 1943 o - Ap 1 2 1 D -R Z 1944 o - Ap 1 0 1 D -R Z 1945 o - Ap 16 0 1 D -R Z 1945 o - N 1 2 0 S -R Z 1946 o - Ap 16 2 1 D -R Z 1946 o - N 1 0 0 S -R Z 1948 o - May 23 0 2 DD -R Z 1948 o - S 1 0 1 D -R Z 1948 1949 - N 1 2 0 S -R Z 1949 o - May 1 0 1 D -R Z 1950 o - Ap 16 0 1 D -R Z 1950 o - S 15 3 0 S -R Z 1951 o - Ap 1 0 1 D -R Z 1951 o - N 11 3 0 S -R Z 1952 o - Ap 20 2 1 D -R Z 1952 o - O 19 3 0 S -R Z 1953 o - Ap 12 2 1 D -R Z 1953 o - S 13 3 0 S -R Z 1954 o - Jun 13 0 1 D -R Z 1954 o - S 12 0 0 S -R Z 1955 o - Jun 11 2 1 D -R Z 1955 o - S 11 0 0 S -R Z 1956 o - Jun 3 0 1 D -R Z 1956 o - S 30 3 0 S -R Z 1957 o - Ap 29 2 1 D -R Z 1957 o - S 22 0 0 S -R Z 1974 o - Jul 7 0 1 D -R Z 1974 o - O 13 0 0 S -R Z 1975 o - Ap 20 0 1 D -R Z 1975 o - Au 31 0 0 S -R Z 1985 o - Ap 14 0 1 D -R Z 1985 o - S 15 0 0 S -R Z 1986 o - May 18 0 1 D -R Z 1986 o - S 7 0 0 S -R Z 1987 o - Ap 15 0 1 D -R Z 1987 o - S 13 0 0 S -R Z 1988 o - Ap 10 0 1 D -R Z 1988 o - S 4 0 0 S -R Z 1989 o - Ap 30 0 1 D -R Z 1989 o - S 3 0 0 S -R Z 1990 o - Mar 25 0 1 D -R Z 1990 o - Au 26 0 0 S -R Z 1991 o - Mar 24 0 1 D -R Z 1991 o - S 1 0 0 S -R Z 1992 o - Mar 29 0 1 D -R Z 1992 o - S 6 0 0 S +R Z 1940 o - May 31 24u 1 D +R Z 1940 o - S 30 24u 0 S +R Z 1940 o - N 16 24u 1 D +R Z 1942 1946 - O 31 24u 0 S +R Z 1943 1944 - Mar 31 24u 1 D +R Z 1945 1946 - Ap 15 24u 1 D +R Z 1948 o - May 22 24u 2 DD +R Z 1948 o - Au 31 24u 1 D +R Z 1948 1949 - O 31 24u 0 S +R Z 1949 o - Ap 30 24u 1 D +R Z 1950 o - Ap 15 24u 1 D +R Z 1950 o - S 14 24u 0 S +R Z 1951 o - Mar 31 24u 1 D +R Z 1951 o - N 10 24u 0 S +R Z 1952 o - Ap 19 24u 1 D +R Z 1952 o - O 18 24u 0 S +R Z 1953 o - Ap 11 24u 1 D +R Z 1953 o - S 12 24u 0 S +R Z 1954 o - Jun 12 24u 1 D +R Z 1954 o - S 11 24u 0 S +R Z 1955 o - Jun 11 24u 1 D +R Z 1955 o - S 10 24u 0 S +R Z 1956 o - Jun 2 24u 1 D +R Z 1956 o - S 29 24u 0 S +R Z 1957 o - Ap 27 24u 1 D +R Z 1957 o - S 21 24u 0 S +R Z 1974 o - Jul 6 24 1 D +R Z 1974 o - O 12 24 0 S +R Z 1975 o - Ap 19 24 1 D +R Z 1975 o - Au 30 24 0 S +R Z 1980 o - Au 2 24s 1 D +R Z 1980 o - S 13 24s 0 S +R Z 1984 o - May 5 24s 1 D +R Z 1984 o - Au 25 24s 0 S +R Z 1985 o - Ap 13 24 1 D +R Z 1985 o - Au 31 24 0 S +R Z 1986 o - May 17 24 1 D +R Z 1986 o - S 6 24 0 S +R Z 1987 o - Ap 14 24 1 D +R Z 1987 o - S 12 24 0 S +R Z 1988 o - Ap 9 24 1 D +R Z 1988 o - S 3 24 0 S +R Z 1989 o - Ap 29 24 1 D +R Z 1989 o - S 2 24 0 S +R Z 1990 o - Mar 24 24 1 D +R Z 1990 o - Au 25 24 0 S +R Z 1991 o - Mar 23 24 1 D +R Z 1991 o - Au 31 24 0 S +R Z 1992 o - Mar 28 24 1 D +R Z 1992 o - S 5 24 0 S R Z 1993 o - Ap 2 0 1 D R Z 1993 o - S 5 0 0 S R Z 1994 o - Ap 1 0 1 D R Z 1994 o - Au 28 0 0 S R Z 1995 o - Mar 31 0 1 D R Z 1995 o - S 3 0 0 S -R Z 1996 o - Mar 15 0 1 D -R Z 1996 o - S 16 0 0 S -R Z 1997 o - Mar 21 0 1 D -R Z 1997 o - S 14 0 0 S +R Z 1996 o - Mar 14 24 1 D +R Z 1996 o - S 15 24 0 S +R Z 1997 o - Mar 20 24 1 D +R Z 1997 o - S 13 24 0 S R Z 1998 o - Mar 20 0 1 D R Z 1998 o - S 6 0 0 S R Z 1999 o - Ap 2 2 1 D @@ -655,27 +742,24 @@ R Z 2003 o - Mar 28 1 1 D R Z 2003 o - O 3 1 0 S R Z 2004 o - Ap 7 1 1 D R Z 2004 o - S 22 1 0 S -R Z 2005 o - Ap 1 2 1 D +R Z 2005 2012 - Ap F<=1 2 1 D R Z 2005 o - O 9 2 0 S -R Z 2006 2010 - Mar F>=26 2 1 D R Z 2006 o - O 1 2 0 S R Z 2007 o - S 16 2 0 S R Z 2008 o - O 5 2 0 S R Z 2009 o - S 27 2 0 S R Z 2010 o - S 12 2 0 S -R Z 2011 o - Ap 1 2 1 D R Z 2011 o - O 2 2 0 S -R Z 2012 o - Mar F>=26 2 1 D R Z 2012 o - S 23 2 0 S R Z 2013 ma - Mar F>=23 2 1 D -R Z 2013 ma - O lastSun 2 0 S +R Z 2013 ma - O lastSu 2 0 S Z Asia/Jerusalem 2:20:54 - LMT 1880 2:20:40 - JMT 1918 2 Z I%sT -R JP 1948 o - May Sat>=1 24 1 D -R JP 1948 1951 - S Sat>=8 25 0 S -R JP 1949 o - Ap Sat>=1 24 1 D -R JP 1950 1951 - May Sat>=1 24 1 D +R JP 1948 o - May Sa>=1 24 1 D +R JP 1948 1951 - S Sa>=8 25 0 S +R JP 1949 o - Ap Sa>=1 24 1 D +R JP 1950 1951 - May Sa>=1 24 1 D Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u 9 JP J%sT R J 1973 o - Jun 6 0 1 S @@ -707,10 +791,12 @@ R J 2004 o - O 15 0s 0 - R J 2005 o - S lastF 0s 0 - R J 2006 2011 - O lastF 0s 0 - R J 2013 o - D 20 0 0 - -R J 2014 ma - Mar lastTh 24 1 S -R J 2014 ma - O lastF 0s 0 - +R J 2014 2021 - Mar lastTh 24 1 S +R J 2014 2022 - O lastF 0s 0 - +R J 2022 o - F lastTh 24 1 S Z Asia/Amman 2:23:44 - LMT 1931 -2 J EE%sT +2 J EE%sT 2022 O 28 0s +3 - +03 Z Asia/Almaty 5:7:48 - LMT 1924 May 2 5 - +05 1930 Jun 21 6 R +06/+07 1991 Mar 31 2s @@ -727,6 +813,16 @@ Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 5 R +05/+06 1992 Ja 19 2s 6 R +06/+07 1992 Mar 29 2s 5 R +05/+06 2004 O 31 2s +6 - +06 2018 D 21 +5 - +05 +Z Asia/Qostanay 4:14:28 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2004 O 31 2s 6 - +06 Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 4 - +04 1930 Jun 21 @@ -765,10 +861,10 @@ Z Asia/Oral 3:25:24 - LMT 1924 May 2 5 R +05/+06 1992 Mar 29 2s 4 R +04/+05 2004 O 31 2s 5 - +05 -R KG 1992 1996 - Ap Sun>=7 0s 1 - -R KG 1992 1996 - S lastSun 0 0 - -R KG 1997 2005 - Mar lastSun 2:30 1 - -R KG 1997 2004 - O lastSun 2:30 0 - +R KG 1992 1996 - Ap Su>=7 0s 1 - +R KG 1992 1996 - S lastSu 0 0 - +R KG 1997 2005 - Mar lastSu 2:30 1 - +R KG 1997 2004 - O lastSu 2:30 0 - Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 5 - +05 1930 Jun 21 6 R +06/+07 1991 Mar 31 2s @@ -776,23 +872,23 @@ Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 5 KG +05/+06 2005 Au 12 6 - +06 R KR 1948 o - Jun 1 0 1 D -R KR 1948 o - S 13 0 0 S +R KR 1948 o - S 12 24 0 S R KR 1949 o - Ap 3 0 1 D -R KR 1949 1951 - S Sun>=8 0 0 S +R KR 1949 1951 - S Sa>=7 24 0 S R KR 1950 o - Ap 1 0 1 D R KR 1951 o - May 6 0 1 D R KR 1955 o - May 5 0 1 D -R KR 1955 o - S 9 0 0 S +R KR 1955 o - S 8 24 0 S R KR 1956 o - May 20 0 1 D -R KR 1956 o - S 30 0 0 S -R KR 1957 1960 - May Sun>=1 0 1 D -R KR 1957 1960 - S Sun>=18 0 0 S -R KR 1987 1988 - May Sun>=8 2 1 D -R KR 1987 1988 - O Sun>=8 3 0 S +R KR 1956 o - S 29 24 0 S +R KR 1957 1960 - May Su>=1 0 1 D +R KR 1957 1960 - S Sa>=17 24 0 S +R KR 1987 1988 - May Su>=8 2 1 D +R KR 1987 1988 - O Su>=8 3 0 S Z Asia/Seoul 8:27:52 - LMT 1908 Ap 8:30 - KST 1912 9 - JST 1945 S 8 -9 - KST 1954 Mar 21 +9 KR K%sT 1954 Mar 21 8:30 KR K%sT 1961 Au 10 9 KR K%sT Z Asia/Pyongyang 8:23 - LMT 1908 Ap @@ -822,22 +918,13 @@ R l 1988 o - Jun 1 0 1 S R l 1989 o - May 10 0 1 S R l 1990 1992 - May 1 0 1 S R l 1992 o - O 4 0 0 - -R l 1993 ma - Mar lastSun 0 1 S -R l 1993 1998 - S lastSun 0 0 - -R l 1999 ma - O lastSun 0 0 - +R l 1993 ma - Mar lastSu 0 1 S +R l 1993 1998 - S lastSu 0 0 - +R l 1999 ma - O lastSu 0 0 - Z Asia/Beirut 2:22 - LMT 1880 2 l EE%sT R NB 1935 1941 - S 14 0 0:20 - R NB 1935 1941 - D 14 0 0 - -Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 -6:55:25 - SMT 1905 Jun -7 - +07 1933 -7 0:20 +0720 1936 -7:20 - +0720 1941 S -7:30 - +0730 1942 F 16 -9 - +09 1945 S 12 -7:30 - +0730 1982 -8 - +08 Z Asia/Kuching 7:21:20 - LMT 1926 Mar 7:30 - +0730 1933 8 NB +08/+0820 1942 F 16 @@ -848,13 +935,13 @@ Z Indian/Maldives 4:54 - LMT 1880 5 - +05 R X 1983 1984 - Ap 1 0 1 - R X 1983 o - O 1 0 0 - -R X 1985 1998 - Mar lastSun 0 1 - -R X 1984 1998 - S lastSun 0 0 - -R X 2001 o - Ap lastSat 2 1 - -R X 2001 2006 - S lastSat 2 0 - -R X 2002 2006 - Mar lastSat 2 1 - -R X 2015 2016 - Mar lastSat 2 1 - -R X 2015 2016 - S lastSat 0 0 - +R X 1985 1998 - Mar lastSu 0 1 - +R X 1984 1998 - S lastSu 0 0 - +R X 2001 o - Ap lastSa 2 1 - +R X 2001 2006 - S lastSa 2 0 - +R X 2002 2006 - Mar lastSa 2 1 - +R X 2015 2016 - Mar lastSa 2 1 - +R X 2015 2016 - S lastSa 0 0 - Z Asia/Hovd 6:6:36 - LMT 1905 Au 6 - +06 1978 7 X +07/+08 @@ -869,8 +956,8 @@ Z Asia/Choibalsan 7:38 - LMT 1905 Au Z Asia/Kathmandu 5:41:16 - LMT 1920 5:30 - +0530 1986 5:45 - +0545 -R PK 2002 o - Ap Sun>=2 0 1 S -R PK 2002 o - O Sun>=2 0 0 - +R PK 2002 o - Ap Su>=2 0 1 S +R PK 2002 o - O Su>=2 0 0 - R PK 2008 o - Jun 1 0 1 S R PK 2008 2009 - N 1 0 0 - R PK 2009 o - Ap 15 0 1 S @@ -886,10 +973,10 @@ R P 2004 o - O 1 1 0 - R P 2005 o - O 4 2 0 - R P 2006 2007 - Ap 1 0 1 S R P 2006 o - S 22 0 0 - -R P 2007 o - S Th>=8 2 0 - +R P 2007 o - S 13 2 0 - R P 2008 2009 - Mar lastF 0 1 S R P 2008 o - S 1 0 0 - -R P 2009 o - S F>=1 1 0 - +R P 2009 o - S 4 1 0 - R P 2010 o - Mar 26 0 1 S R P 2010 o - Au 11 0 0 - R P 2011 o - Ap 1 0:1 1 S @@ -898,11 +985,20 @@ R P 2011 o - Au 30 0 1 S R P 2011 o - S 30 0 0 - R P 2012 2014 - Mar lastTh 24 1 S R P 2012 o - S 21 1 0 - -R P 2013 o - S F>=21 0 0 - -R P 2014 2015 - O F>=21 0 0 - -R P 2015 o - Mar lastF 24 1 S -R P 2016 ma - Mar Sat>=22 1 1 S -R P 2016 ma - O lastSat 1 0 - +R P 2013 o - S 27 0 0 - +R P 2014 o - O 24 0 0 - +R P 2015 o - Mar 28 0 1 S +R P 2015 o - O 23 1 0 - +R P 2016 2018 - Mar Sa<=30 1 1 S +R P 2016 2018 - O Sa<=30 1 0 - +R P 2019 o - Mar 29 0 1 S +R P 2019 o - O Sa<=30 0 0 - +R P 2020 2021 - Mar Sa<=30 0 1 S +R P 2020 o - O 24 1 0 - +R P 2021 o - O 29 1 0 - +R P 2022 o - Mar 27 0 1 S +R P 2022 ma - O Sa<=30 2 0 - +R P 2023 ma - Mar Sa<=30 2 1 S Z Asia/Gaza 2:17:52 - LMT 1900 O 2 Z EET/EEST 1948 May 15 2 K EE%sT 1967 Jun 5 @@ -935,11 +1031,8 @@ Z Asia/Manila -15:56 - LMT 1844 D 31 Z Asia/Qatar 3:26:8 - LMT 1920 4 - +04 1972 Jun 3 - +03 -Li Asia/Qatar Asia/Bahrain Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 3 - +03 -Li Asia/Riyadh Asia/Aden -Li Asia/Riyadh Asia/Kuwait Z Asia/Singapore 6:55:25 - LMT 1901 6:55:25 - SMT 1905 Jun 7 - +07 1933 @@ -958,8 +1051,8 @@ Z Asia/Colombo 5:19:24 - LMT 1880 6:30 - +0630 1996 O 26 0:30 6 - +06 2006 Ap 15 0:30 5:30 - +0530 -R S 1920 1923 - Ap Sun>=15 2 1 S -R S 1920 1923 - O Sun>=1 2 0 - +R S 1920 1923 - Ap Su>=15 2 1 S +R S 1920 1923 - O Su>=1 2 0 - R S 1962 o - Ap 29 2 1 S R S 1962 o - O 1 2 0 - R S 1963 1965 - May 1 2 1 S @@ -997,20 +1090,19 @@ R S 2008 o - Ap F>=1 0 1 S R S 2008 o - N 1 0 0 - R S 2009 o - Mar lastF 0 1 S R S 2010 2011 - Ap F>=1 0 1 S -R S 2012 ma - Mar lastF 0 1 S -R S 2009 ma - O lastF 0 0 - +R S 2012 2022 - Mar lastF 0 1 S +R S 2009 2022 - O lastF 0 0 - Z Asia/Damascus 2:25:12 - LMT 1920 -2 S EE%sT +2 S EE%sT 2022 O 28 +3 - +03 Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 5 - +05 1930 Jun 21 6 R +06/+07 1991 Mar 31 2s -5 1 +05/+06 1991 S 9 2s +5 1 +06 1991 S 9 2s 5 - +05 Z Asia/Bangkok 6:42:4 - LMT 1880 6:42:4 - BMT 1920 Ap 7 - +07 -Li Asia/Bangkok Asia/Phnom_Penh -Li Asia/Bangkok Asia/Vientiane Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 4 - +04 1930 Jun 21 5 R +05/+06 1991 Mar 31 2 @@ -1018,7 +1110,6 @@ Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 5 - +05 Z Asia/Dubai 3:41:12 - LMT 1920 4 - +04 -Li Asia/Dubai Asia/Muscat Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 4 - +04 1930 Jun 21 5 - +05 1981 Ap @@ -1031,7 +1122,7 @@ Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 6 R +06/+07 1991 Mar 31 2 5 R +05/+06 1992 5 - +05 -Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul +Z Asia/Ho_Chi_Minh 7:6:30 - LMT 1906 Jul 7:6:30 - PLMT 1911 May 7 - +07 1942 D 31 23 8 - +08 1945 Mar 14 23 @@ -1041,37 +1132,37 @@ Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul 7 - +07 1959 D 31 23 8 - +08 1975 Jun 13 7 - +07 -R AU 1917 o - Ja 1 0:1 1 D -R AU 1917 o - Mar 25 2 0 S -R AU 1942 o - Ja 1 2 1 D -R AU 1942 o - Mar 29 2 0 S -R AU 1942 o - S 27 2 1 D -R AU 1943 1944 - Mar lastSun 2 0 S -R AU 1943 o - O 3 2 1 D +R AU 1917 o - Ja 1 2s 1 D +R AU 1917 o - Mar lastSu 2s 0 S +R AU 1942 o - Ja 1 2s 1 D +R AU 1942 o - Mar lastSu 2s 0 S +R AU 1942 o - S 27 2s 1 D +R AU 1943 1944 - Mar lastSu 2s 0 S +R AU 1943 o - O 3 2s 1 D Z Australia/Darwin 8:43:20 - LMT 1895 F 9 - ACST 1899 May 9:30 AU AC%sT -R AW 1974 o - O lastSun 2s 1 D -R AW 1975 o - Mar Sun>=1 2s 0 S -R AW 1983 o - O lastSun 2s 1 D -R AW 1984 o - Mar Sun>=1 2s 0 S +R AW 1974 o - O lastSu 2s 1 D +R AW 1975 o - Mar Su>=1 2s 0 S +R AW 1983 o - O lastSu 2s 1 D +R AW 1984 o - Mar Su>=1 2s 0 S R AW 1991 o - N 17 2s 1 D -R AW 1992 o - Mar Sun>=1 2s 0 S +R AW 1992 o - Mar Su>=1 2s 0 S R AW 2006 o - D 3 2s 1 D -R AW 2007 2009 - Mar lastSun 2s 0 S -R AW 2007 2008 - O lastSun 2s 1 D +R AW 2007 2009 - Mar lastSu 2s 0 S +R AW 2007 2008 - O lastSu 2s 1 D Z Australia/Perth 7:43:24 - LMT 1895 D 8 AU AW%sT 1943 Jul 8 AW AW%sT Z Australia/Eucla 8:35:28 - LMT 1895 D 8:45 AU +0845/+0945 1943 Jul 8:45 AW +0845/+0945 -R AQ 1971 o - O lastSun 2s 1 D -R AQ 1972 o - F lastSun 2s 0 S -R AQ 1989 1991 - O lastSun 2s 1 D -R AQ 1990 1992 - Mar Sun>=1 2s 0 S -R Ho 1992 1993 - O lastSun 2s 1 D -R Ho 1993 1994 - Mar Sun>=1 2s 0 S +R AQ 1971 o - O lastSu 2s 1 D +R AQ 1972 o - F lastSu 2s 0 S +R AQ 1989 1991 - O lastSu 2s 1 D +R AQ 1990 1992 - Mar Su>=1 2s 0 S +R Ho 1992 1993 - O lastSu 2s 1 D +R Ho 1993 1994 - Mar Su>=1 2s 0 S Z Australia/Brisbane 10:12:8 - LMT 1895 10 AU AE%sT 1971 10 AQ AE%sT @@ -1079,87 +1170,85 @@ Z Australia/Lindeman 9:55:56 - LMT 1895 10 AU AE%sT 1971 10 AQ AE%sT 1992 Jul 10 Ho AE%sT -R AS 1971 1985 - O lastSun 2s 1 D +R AS 1971 1985 - O lastSu 2s 1 D R AS 1986 o - O 19 2s 1 D -R AS 1987 2007 - O lastSun 2s 1 D +R AS 1987 2007 - O lastSu 2s 1 D R AS 1972 o - F 27 2s 0 S -R AS 1973 1985 - Mar Sun>=1 2s 0 S -R AS 1986 1990 - Mar Sun>=15 2s 0 S +R AS 1973 1985 - Mar Su>=1 2s 0 S +R AS 1986 1990 - Mar Su>=15 2s 0 S R AS 1991 o - Mar 3 2s 0 S R AS 1992 o - Mar 22 2s 0 S R AS 1993 o - Mar 7 2s 0 S R AS 1994 o - Mar 20 2s 0 S -R AS 1995 2005 - Mar lastSun 2s 0 S +R AS 1995 2005 - Mar lastSu 2s 0 S R AS 2006 o - Ap 2 2s 0 S -R AS 2007 o - Mar lastSun 2s 0 S -R AS 2008 ma - Ap Sun>=1 2s 0 S -R AS 2008 ma - O Sun>=1 2s 1 D +R AS 2007 o - Mar lastSu 2s 0 S +R AS 2008 ma - Ap Su>=1 2s 0 S +R AS 2008 ma - O Su>=1 2s 1 D Z Australia/Adelaide 9:14:20 - LMT 1895 F 9 - ACST 1899 May 9:30 AU AC%sT 1971 9:30 AS AC%sT -R AT 1967 o - O Sun>=1 2s 1 D -R AT 1968 o - Mar lastSun 2s 0 S -R AT 1968 1985 - O lastSun 2s 1 D -R AT 1969 1971 - Mar Sun>=8 2s 0 S -R AT 1972 o - F lastSun 2s 0 S -R AT 1973 1981 - Mar Sun>=1 2s 0 S -R AT 1982 1983 - Mar lastSun 2s 0 S -R AT 1984 1986 - Mar Sun>=1 2s 0 S -R AT 1986 o - O Sun>=15 2s 1 D -R AT 1987 1990 - Mar Sun>=15 2s 0 S -R AT 1987 o - O Sun>=22 2s 1 D -R AT 1988 1990 - O lastSun 2s 1 D -R AT 1991 1999 - O Sun>=1 2s 1 D -R AT 1991 2005 - Mar lastSun 2s 0 S -R AT 2000 o - Au lastSun 2s 1 D -R AT 2001 ma - O Sun>=1 2s 1 D -R AT 2006 o - Ap Sun>=1 2s 0 S -R AT 2007 o - Mar lastSun 2s 0 S -R AT 2008 ma - Ap Sun>=1 2s 0 S +R AT 1916 o - O Su>=1 2s 1 D +R AT 1917 o - Mar lastSu 2s 0 S +R AT 1917 1918 - O Su>=22 2s 1 D +R AT 1918 1919 - Mar Su>=1 2s 0 S +R AT 1967 o - O Su>=1 2s 1 D +R AT 1968 o - Mar Su>=29 2s 0 S +R AT 1968 1985 - O lastSu 2s 1 D +R AT 1969 1971 - Mar Su>=8 2s 0 S +R AT 1972 o - F lastSu 2s 0 S +R AT 1973 1981 - Mar Su>=1 2s 0 S +R AT 1982 1983 - Mar lastSu 2s 0 S +R AT 1984 1986 - Mar Su>=1 2s 0 S +R AT 1986 o - O Su>=15 2s 1 D +R AT 1987 1990 - Mar Su>=15 2s 0 S +R AT 1987 o - O Su>=22 2s 1 D +R AT 1988 1990 - O lastSu 2s 1 D +R AT 1991 1999 - O Su>=1 2s 1 D +R AT 1991 2005 - Mar lastSu 2s 0 S +R AT 2000 o - Au lastSu 2s 1 D +R AT 2001 ma - O Su>=1 2s 1 D +R AT 2006 o - Ap Su>=1 2s 0 S +R AT 2007 o - Mar lastSu 2s 0 S +R AT 2008 ma - Ap Su>=1 2s 0 S Z Australia/Hobart 9:49:16 - LMT 1895 S -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F +10 AT AE%sT 1919 O 24 10 AU AE%sT 1967 10 AT AE%sT -Z Australia/Currie 9:35:28 - LMT 1895 S -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F -10 AU AE%sT 1971 Jul -10 AT AE%sT -R AV 1971 1985 - O lastSun 2s 1 D -R AV 1972 o - F lastSun 2s 0 S -R AV 1973 1985 - Mar Sun>=1 2s 0 S -R AV 1986 1990 - Mar Sun>=15 2s 0 S -R AV 1986 1987 - O Sun>=15 2s 1 D -R AV 1988 1999 - O lastSun 2s 1 D -R AV 1991 1994 - Mar Sun>=1 2s 0 S -R AV 1995 2005 - Mar lastSun 2s 0 S -R AV 2000 o - Au lastSun 2s 1 D -R AV 2001 2007 - O lastSun 2s 1 D -R AV 2006 o - Ap Sun>=1 2s 0 S -R AV 2007 o - Mar lastSun 2s 0 S -R AV 2008 ma - Ap Sun>=1 2s 0 S -R AV 2008 ma - O Sun>=1 2s 1 D +R AV 1971 1985 - O lastSu 2s 1 D +R AV 1972 o - F lastSu 2s 0 S +R AV 1973 1985 - Mar Su>=1 2s 0 S +R AV 1986 1990 - Mar Su>=15 2s 0 S +R AV 1986 1987 - O Su>=15 2s 1 D +R AV 1988 1999 - O lastSu 2s 1 D +R AV 1991 1994 - Mar Su>=1 2s 0 S +R AV 1995 2005 - Mar lastSu 2s 0 S +R AV 2000 o - Au lastSu 2s 1 D +R AV 2001 2007 - O lastSu 2s 1 D +R AV 2006 o - Ap Su>=1 2s 0 S +R AV 2007 o - Mar lastSu 2s 0 S +R AV 2008 ma - Ap Su>=1 2s 0 S +R AV 2008 ma - O Su>=1 2s 1 D Z Australia/Melbourne 9:39:52 - LMT 1895 F 10 AU AE%sT 1971 10 AV AE%sT -R AN 1971 1985 - O lastSun 2s 1 D +R AN 1971 1985 - O lastSu 2s 1 D R AN 1972 o - F 27 2s 0 S -R AN 1973 1981 - Mar Sun>=1 2s 0 S -R AN 1982 o - Ap Sun>=1 2s 0 S -R AN 1983 1985 - Mar Sun>=1 2s 0 S -R AN 1986 1989 - Mar Sun>=15 2s 0 S +R AN 1973 1981 - Mar Su>=1 2s 0 S +R AN 1982 o - Ap Su>=1 2s 0 S +R AN 1983 1985 - Mar Su>=1 2s 0 S +R AN 1986 1989 - Mar Su>=15 2s 0 S R AN 1986 o - O 19 2s 1 D -R AN 1987 1999 - O lastSun 2s 1 D -R AN 1990 1995 - Mar Sun>=1 2s 0 S -R AN 1996 2005 - Mar lastSun 2s 0 S -R AN 2000 o - Au lastSun 2s 1 D -R AN 2001 2007 - O lastSun 2s 1 D -R AN 2006 o - Ap Sun>=1 2s 0 S -R AN 2007 o - Mar lastSun 2s 0 S -R AN 2008 ma - Ap Sun>=1 2s 0 S -R AN 2008 ma - O Sun>=1 2s 1 D +R AN 1987 1999 - O lastSu 2s 1 D +R AN 1990 1995 - Mar Su>=1 2s 0 S +R AN 1996 2005 - Mar lastSu 2s 0 S +R AN 2000 o - Au lastSu 2s 1 D +R AN 2001 2007 - O lastSu 2s 1 D +R AN 2006 o - Ap Su>=1 2s 0 S +R AN 2007 o - Mar lastSu 2s 0 S +R AN 2008 ma - Ap Su>=1 2s 0 S +R AN 2008 ma - O Su>=1 2s 1 D Z Australia/Sydney 10:4:52 - LMT 1895 F 10 AU AE%sT 1971 10 AN AE%sT @@ -1169,20 +1258,20 @@ Z Australia/Broken_Hill 9:25:48 - LMT 1895 F 9:30 AU AC%sT 1971 9:30 AN AC%sT 2000 9:30 AS AC%sT -R LH 1981 1984 - O lastSun 2 1 - -R LH 1982 1985 - Mar Sun>=1 2 0 - -R LH 1985 o - O lastSun 2 0:30 - -R LH 1986 1989 - Mar Sun>=15 2 0 - +R LH 1981 1984 - O lastSu 2 1 - +R LH 1982 1985 - Mar Su>=1 2 0 - +R LH 1985 o - O lastSu 2 0:30 - +R LH 1986 1989 - Mar Su>=15 2 0 - R LH 1986 o - O 19 2 0:30 - -R LH 1987 1999 - O lastSun 2 0:30 - -R LH 1990 1995 - Mar Sun>=1 2 0 - -R LH 1996 2005 - Mar lastSun 2 0 - -R LH 2000 o - Au lastSun 2 0:30 - -R LH 2001 2007 - O lastSun 2 0:30 - -R LH 2006 o - Ap Sun>=1 2 0 - -R LH 2007 o - Mar lastSun 2 0 - -R LH 2008 ma - Ap Sun>=1 2 0 - -R LH 2008 ma - O Sun>=1 2 0:30 - +R LH 1987 1999 - O lastSu 2 0:30 - +R LH 1990 1995 - Mar Su>=1 2 0 - +R LH 1996 2005 - Mar lastSu 2 0 - +R LH 2000 o - Au lastSu 2 0:30 - +R LH 2001 2007 - O lastSu 2 0:30 - +R LH 2006 o - Ap Su>=1 2 0 - +R LH 2007 o - Mar lastSu 2 0 - +R LH 2008 ma - Ap Su>=1 2 0 - +R LH 2008 ma - O Su>=1 2 0:30 - Z Australia/Lord_Howe 10:36:20 - LMT 1895 F 10 - AEST 1981 Mar 10:30 LH +1030/+1130 1985 Jul @@ -1193,22 +1282,21 @@ Z Antarctica/Macquarie 0 - -00 1899 N 10 AU AE%sT 1919 Ap 1 0s 0 - -00 1948 Mar 25 10 AU AE%sT 1967 -10 AT AE%sT 2010 Ap 4 3 -11 - +11 -Z Indian/Christmas 7:2:52 - LMT 1895 F -7 - +07 -Z Indian/Cocos 6:27:40 - LMT 1900 -6:30 - +0630 -R FJ 1998 1999 - N Sun>=1 2 1 - -R FJ 1999 2000 - F lastSun 3 0 - +10 AT AE%sT 2010 +10 1 AEDT 2011 +10 AT AE%sT +R FJ 1998 1999 - N Su>=1 2 1 - +R FJ 1999 2000 - F lastSu 3 0 - R FJ 2009 o - N 29 2 1 - -R FJ 2010 o - Mar lastSun 3 0 - -R FJ 2010 2013 - O Sun>=21 2 1 - -R FJ 2011 o - Mar Sun>=1 3 0 - -R FJ 2012 2013 - Ja Sun>=18 3 0 - -R FJ 2014 o - Ja Sun>=18 2 0 - -R FJ 2014 ma - N Sun>=1 2 1 - -R FJ 2015 ma - Ja Sun>=13 3 0 - +R FJ 2010 o - Mar lastSu 3 0 - +R FJ 2010 2013 - O Su>=21 2 1 - +R FJ 2011 o - Mar Su>=1 3 0 - +R FJ 2012 2013 - Ja Su>=18 3 0 - +R FJ 2014 o - Ja Su>=18 2 0 - +R FJ 2014 2018 - N Su>=1 2 1 - +R FJ 2015 2021 - Ja Su>=12 3 0 - +R FJ 2019 o - N Su>=8 2 1 - +R FJ 2020 o - D 20 2 1 - Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 12 FJ +12/+13 Z Pacific/Gambier -8:59:48 - LMT 1912 O @@ -1217,14 +1305,29 @@ Z Pacific/Marquesas -9:18 - LMT 1912 O -9:30 - -0930 Z Pacific/Tahiti -9:58:16 - LMT 1912 O -10 - -10 +R Gu 1959 o - Jun 27 2 1 D +R Gu 1961 o - Ja 29 2 0 S +R Gu 1967 o - S 1 2 1 D +R Gu 1969 o - Ja 26 0:1 0 S +R Gu 1969 o - Jun 22 2 1 D +R Gu 1969 o - Au 31 2 0 S +R Gu 1970 1971 - Ap lastSu 2 1 D +R Gu 1970 1971 - S Su>=1 2 0 S +R Gu 1973 o - D 16 2 1 D +R Gu 1974 o - F 24 2 0 S +R Gu 1976 o - May 26 2 1 D +R Gu 1976 o - Au 22 2:1 0 S +R Gu 1977 o - Ap 24 2 1 D +R Gu 1977 o - Au 28 2 0 S Z Pacific/Guam -14:21 - LMT 1844 D 31 9:39 - LMT 1901 -10 - GST 2000 D 23 +10 - GST 1941 D 10 +9 - +09 1944 Jul 31 +10 Gu G%sT 2000 D 23 10 - ChST -Li Pacific/Guam Pacific/Saipan Z Pacific/Tarawa 11:32:4 - LMT 1901 12 - +12 -Z Pacific/Enderbury -11:24:20 - LMT 1901 +Z Pacific/Kanton 0 - -00 1937 Au 31 -12 - -12 1979 O -11 - -11 1994 D 31 13 - +13 @@ -1232,27 +1335,29 @@ Z Pacific/Kiritimati -10:29:20 - LMT 1901 -10:40 - -1040 1979 O -10 - -10 1994 D 31 14 - +14 -Z Pacific/Majuro 11:24:48 - LMT 1901 -11 - +11 1969 O -12 - +12 Z Pacific/Kwajalein 11:9:20 - LMT 1901 +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1944 F 6 11 - +11 1969 O --12 - -12 1993 Au 20 +-12 - -12 1993 Au 20 24 12 - +12 -Z Pacific/Chuuk 10:7:8 - LMT 1901 -10 - +10 -Z Pacific/Pohnpei 10:32:52 - LMT 1901 -11 - +11 -Z Pacific/Kosrae 10:51:56 - LMT 1901 +Z Pacific/Kosrae -13:8:4 - LMT 1844 D 31 +10:51:56 - LMT 1901 +11 - +11 1914 O +9 - +09 1919 F +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1945 Au 11 - +11 1969 O 12 - +12 1999 11 - +11 Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 -11:30 - +1130 1942 Mar 15 -9 - +09 1944 Au 15 -11:30 - +1130 1979 May +11:30 - +1130 1942 Au 29 +9 - +09 1945 S 8 +11:30 - +1130 1979 F 10 2 12 - +12 -R NC 1977 1978 - D Sun>=1 0 1 - +R NC 1977 1978 - D Su>=1 0 1 - R NC 1978 1979 - F 27 0 0 - R NC 1996 o - D 1 2s 1 - R NC 1997 o - Mar 2 2s 0 - @@ -1260,53 +1365,54 @@ Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 11 NC +11/+12 R NZ 1927 o - N 6 2 1 S R NZ 1928 o - Mar 4 2 0 M -R NZ 1928 1933 - O Sun>=8 2 0:30 S -R NZ 1929 1933 - Mar Sun>=15 2 0 M -R NZ 1934 1940 - Ap lastSun 2 0 M -R NZ 1934 1940 - S lastSun 2 0:30 S +R NZ 1928 1933 - O Su>=8 2 0:30 S +R NZ 1929 1933 - Mar Su>=15 2 0 M +R NZ 1934 1940 - Ap lastSu 2 0 M +R NZ 1934 1940 - S lastSu 2 0:30 S R NZ 1946 o - Ja 1 0 0 S -R NZ 1974 o - N Sun>=1 2s 1 D -R k 1974 o - N Sun>=1 2:45s 1 - -R NZ 1975 o - F lastSun 2s 0 S -R k 1975 o - F lastSun 2:45s 0 - -R NZ 1975 1988 - O lastSun 2s 1 D -R k 1975 1988 - O lastSun 2:45s 1 - -R NZ 1976 1989 - Mar Sun>=1 2s 0 S -R k 1976 1989 - Mar Sun>=1 2:45s 0 - -R NZ 1989 o - O Sun>=8 2s 1 D -R k 1989 o - O Sun>=8 2:45s 1 - -R NZ 1990 2006 - O Sun>=1 2s 1 D -R k 1990 2006 - O Sun>=1 2:45s 1 - -R NZ 1990 2007 - Mar Sun>=15 2s 0 S -R k 1990 2007 - Mar Sun>=15 2:45s 0 - -R NZ 2007 ma - S lastSun 2s 1 D -R k 2007 ma - S lastSun 2:45s 1 - -R NZ 2008 ma - Ap Sun>=1 2s 0 S -R k 2008 ma - Ap Sun>=1 2:45s 0 - +R NZ 1974 o - N Su>=1 2s 1 D +R k 1974 o - N Su>=1 2:45s 1 - +R NZ 1975 o - F lastSu 2s 0 S +R k 1975 o - F lastSu 2:45s 0 - +R NZ 1975 1988 - O lastSu 2s 1 D +R k 1975 1988 - O lastSu 2:45s 1 - +R NZ 1976 1989 - Mar Su>=1 2s 0 S +R k 1976 1989 - Mar Su>=1 2:45s 0 - +R NZ 1989 o - O Su>=8 2s 1 D +R k 1989 o - O Su>=8 2:45s 1 - +R NZ 1990 2006 - O Su>=1 2s 1 D +R k 1990 2006 - O Su>=1 2:45s 1 - +R NZ 1990 2007 - Mar Su>=15 2s 0 S +R k 1990 2007 - Mar Su>=15 2:45s 0 - +R NZ 2007 ma - S lastSu 2s 1 D +R k 2007 ma - S lastSu 2:45s 1 - +R NZ 2008 ma - Ap Su>=1 2s 0 S +R k 2008 ma - Ap Su>=1 2:45s 0 - Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 11:30 NZ NZ%sT 1946 12 NZ NZ%sT Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 12:15 - +1215 1946 12:45 k +1245/+1345 -Li Pacific/Auckland Antarctica/McMurdo R CK 1978 o - N 12 0 0:30 - -R CK 1979 1991 - Mar Sun>=1 0 0 - -R CK 1979 1990 - O lastSun 0 0:30 - -Z Pacific/Rarotonga -10:39:4 - LMT 1901 +R CK 1979 1991 - Mar Su>=1 0 0 - +R CK 1979 1990 - O lastSu 0 0:30 - +Z Pacific/Rarotonga 13:20:56 - LMT 1899 D 26 +-10:39:4 - LMT 1952 O 16 -10:30 - -1030 1978 N 12 -10 CK -10/-0930 -Z Pacific/Niue -11:19:40 - LMT 1901 --11:20 - -1120 1951 --11:30 - -1130 1978 O +Z Pacific/Niue -11:19:40 - LMT 1952 O 16 +-11:20 - -1120 1964 Jul -11 - -11 Z Pacific/Norfolk 11:11:52 - LMT 1901 11:12 - +1112 1951 -11:30 - +1130 1974 O 27 2 -11:30 1 +1230 1975 Mar 2 2 -11:30 - +1130 2015 O 4 2 -11 - +11 -Z Pacific/Palau 8:57:56 - LMT 1901 +11:30 - +1130 1974 O 27 2s +11:30 1 +1230 1975 Mar 2 2s +11:30 - +1130 2015 O 4 2s +11 - +11 2019 Jul +11 AN +11/+12 +Z Pacific/Palau -15:2:4 - LMT 1844 D 31 +8:57:56 - LMT 1901 9 - +09 Z Pacific/Port_Moresby 9:48:40 - LMT 1880 9:48:32 - PMMT 1895 @@ -1323,12 +1429,11 @@ Z Pacific/Pitcairn -8:40:20 - LMT 1901 Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 -11:22:48 - LMT 1911 -11 - SST -Li Pacific/Pago_Pago Pacific/Midway -R WS 2010 o - S lastSun 0 1 - -R WS 2011 o - Ap Sat>=1 4 0 - -R WS 2011 o - S lastSat 3 1 - -R WS 2012 ma - Ap Sun>=1 4 0 - -R WS 2012 ma - S lastSun 3 1 - +R WS 2010 o - S lastSu 0 1 - +R WS 2011 o - Ap Sa>=1 4 0 - +R WS 2011 o - S lastSa 3 1 - +R WS 2012 2021 - Ap Su>=1 4 0 - +R WS 2012 2020 - S lastSu 3 1 - Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 -11:26:56 - LMT 1911 -11:30 - -1130 1950 @@ -1341,28 +1446,22 @@ Z Pacific/Fakaofo -11:24:56 - LMT 1901 13 - +13 R TO 1999 o - O 7 2s 1 - R TO 2000 o - Mar 19 2s 0 - -R TO 2000 2001 - N Sun>=1 2 1 - -R TO 2001 2002 - Ja lastSun 2 0 - -R TO 2016 o - N Sun>=1 2 1 - -R TO 2017 o - Ja Sun>=15 3 0 - -Z Pacific/Tongatapu 12:19:20 - LMT 1901 -12:20 - +1220 1941 +R TO 2000 2001 - N Su>=1 2 1 - +R TO 2001 2002 - Ja lastSu 2 0 - +R TO 2016 o - N Su>=1 2 1 - +R TO 2017 o - Ja Su>=15 3 0 - +Z Pacific/Tongatapu 12:19:12 - LMT 1945 S 10 +12:20 - +1220 1961 13 - +13 1999 13 TO +13/+14 -Z Pacific/Funafuti 11:56:52 - LMT 1901 -12 - +12 -Z Pacific/Wake 11:6:28 - LMT 1901 -12 - +12 -R VU 1983 o - S 25 0 1 - -R VU 1984 1991 - Mar Sun>=23 0 0 - -R VU 1984 o - O 23 0 1 - -R VU 1985 1991 - S Sun>=23 0 1 - -R VU 1992 1993 - Ja Sun>=23 0 0 - -R VU 1992 o - O Sun>=23 0 1 - +R VU 1973 o - D 22 12u 1 - +R VU 1974 o - Mar 30 12u 0 - +R VU 1983 1991 - S Sa>=22 24 1 - +R VU 1984 1991 - Mar Sa>=22 24 0 - +R VU 1992 1993 - Ja Sa>=22 24 0 - +R VU 1992 o - O Sa>=22 24 1 - Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 11 VU +11/+12 -Z Pacific/Wallis 12:15:20 - LMT 1901 -12 - +12 R G 1916 o - May 21 2s 1 BST R G 1916 o - O 1 2s 0 GMT R G 1917 o - Ap 8 2s 1 BST @@ -1377,31 +1476,31 @@ R G 1921 o - Ap 3 2s 1 BST R G 1921 o - O 3 2s 0 GMT R G 1922 o - Mar 26 2s 1 BST R G 1922 o - O 8 2s 0 GMT -R G 1923 o - Ap Sun>=16 2s 1 BST -R G 1923 1924 - S Sun>=16 2s 0 GMT -R G 1924 o - Ap Sun>=9 2s 1 BST -R G 1925 1926 - Ap Sun>=16 2s 1 BST -R G 1925 1938 - O Sun>=2 2s 0 GMT -R G 1927 o - Ap Sun>=9 2s 1 BST -R G 1928 1929 - Ap Sun>=16 2s 1 BST -R G 1930 o - Ap Sun>=9 2s 1 BST -R G 1931 1932 - Ap Sun>=16 2s 1 BST -R G 1933 o - Ap Sun>=9 2s 1 BST -R G 1934 o - Ap Sun>=16 2s 1 BST -R G 1935 o - Ap Sun>=9 2s 1 BST -R G 1936 1937 - Ap Sun>=16 2s 1 BST -R G 1938 o - Ap Sun>=9 2s 1 BST -R G 1939 o - Ap Sun>=16 2s 1 BST -R G 1939 o - N Sun>=16 2s 0 GMT -R G 1940 o - F Sun>=23 2s 1 BST -R G 1941 o - May Sun>=2 1s 2 BDST -R G 1941 1943 - Au Sun>=9 1s 1 BST -R G 1942 1944 - Ap Sun>=2 1s 2 BDST -R G 1944 o - S Sun>=16 1s 1 BST +R G 1923 o - Ap Su>=16 2s 1 BST +R G 1923 1924 - S Su>=16 2s 0 GMT +R G 1924 o - Ap Su>=9 2s 1 BST +R G 1925 1926 - Ap Su>=16 2s 1 BST +R G 1925 1938 - O Su>=2 2s 0 GMT +R G 1927 o - Ap Su>=9 2s 1 BST +R G 1928 1929 - Ap Su>=16 2s 1 BST +R G 1930 o - Ap Su>=9 2s 1 BST +R G 1931 1932 - Ap Su>=16 2s 1 BST +R G 1933 o - Ap Su>=9 2s 1 BST +R G 1934 o - Ap Su>=16 2s 1 BST +R G 1935 o - Ap Su>=9 2s 1 BST +R G 1936 1937 - Ap Su>=16 2s 1 BST +R G 1938 o - Ap Su>=9 2s 1 BST +R G 1939 o - Ap Su>=16 2s 1 BST +R G 1939 o - N Su>=16 2s 0 GMT +R G 1940 o - F Su>=23 2s 1 BST +R G 1941 o - May Su>=2 1s 2 BDST +R G 1941 1943 - Au Su>=9 1s 1 BST +R G 1942 1944 - Ap Su>=2 1s 2 BDST +R G 1944 o - S Su>=16 1s 1 BST R G 1945 o - Ap M>=2 1s 2 BDST -R G 1945 o - Jul Sun>=9 1s 1 BST -R G 1945 1946 - O Sun>=2 2s 0 GMT -R G 1946 o - Ap Sun>=9 2s 1 BST +R G 1945 o - Jul Su>=9 1s 1 BST +R G 1945 1946 - O Su>=2 2s 0 GMT +R G 1946 o - Ap Su>=9 2s 1 BST R G 1947 o - Mar 16 2s 1 BST R G 1947 o - Ap 13 1s 2 BDST R G 1947 o - Au 10 1s 1 BST @@ -1410,40 +1509,37 @@ R G 1948 o - Mar 14 2s 1 BST R G 1948 o - O 31 2s 0 GMT R G 1949 o - Ap 3 2s 1 BST R G 1949 o - O 30 2s 0 GMT -R G 1950 1952 - Ap Sun>=14 2s 1 BST -R G 1950 1952 - O Sun>=21 2s 0 GMT -R G 1953 o - Ap Sun>=16 2s 1 BST -R G 1953 1960 - O Sun>=2 2s 0 GMT -R G 1954 o - Ap Sun>=9 2s 1 BST -R G 1955 1956 - Ap Sun>=16 2s 1 BST -R G 1957 o - Ap Sun>=9 2s 1 BST -R G 1958 1959 - Ap Sun>=16 2s 1 BST -R G 1960 o - Ap Sun>=9 2s 1 BST -R G 1961 1963 - Mar lastSun 2s 1 BST -R G 1961 1968 - O Sun>=23 2s 0 GMT -R G 1964 1967 - Mar Sun>=19 2s 1 BST +R G 1950 1952 - Ap Su>=14 2s 1 BST +R G 1950 1952 - O Su>=21 2s 0 GMT +R G 1953 o - Ap Su>=16 2s 1 BST +R G 1953 1960 - O Su>=2 2s 0 GMT +R G 1954 o - Ap Su>=9 2s 1 BST +R G 1955 1956 - Ap Su>=16 2s 1 BST +R G 1957 o - Ap Su>=9 2s 1 BST +R G 1958 1959 - Ap Su>=16 2s 1 BST +R G 1960 o - Ap Su>=9 2s 1 BST +R G 1961 1963 - Mar lastSu 2s 1 BST +R G 1961 1968 - O Su>=23 2s 0 GMT +R G 1964 1967 - Mar Su>=19 2s 1 BST R G 1968 o - F 18 2s 1 BST -R G 1972 1980 - Mar Sun>=16 2s 1 BST -R G 1972 1980 - O Sun>=23 2s 0 GMT -R G 1981 1995 - Mar lastSun 1u 1 BST -R G 1981 1989 - O Sun>=23 1u 0 GMT -R G 1990 1995 - O Sun>=22 1u 0 GMT -Z Europe/London -0:1:15 - LMT 1847 D 1 0s +R G 1972 1980 - Mar Su>=16 2s 1 BST +R G 1972 1980 - O Su>=23 2s 0 GMT +R G 1981 1995 - Mar lastSu 1u 1 BST +R G 1981 1989 - O Su>=23 1u 0 GMT +R G 1990 1995 - O Su>=22 1u 0 GMT +Z Europe/London -0:1:15 - LMT 1847 D 0 G %s 1968 O 27 1 - BST 1971 O 31 2u 0 G %s 1996 0 E GMT/BST -Li Europe/London Europe/Jersey -Li Europe/London Europe/Guernsey -Li Europe/London Europe/Isle_of_Man R IE 1971 o - O 31 2u -1 - -R IE 1972 1980 - Mar Sun>=16 2u 0 - -R IE 1972 1980 - O Sun>=23 2u -1 - -R IE 1981 ma - Mar lastSun 1u 0 - -R IE 1981 1989 - O Sun>=23 1u -1 - -R IE 1990 1995 - O Sun>=22 1u -1 - -R IE 1996 ma - O lastSun 1u -1 - -Z Europe/Dublin -0:25 - LMT 1880 Au 2 +R IE 1972 1980 - Mar Su>=16 2u 0 - +R IE 1972 1980 - O Su>=23 2u -1 - +R IE 1981 ma - Mar lastSu 1u 0 - +R IE 1981 1989 - O Su>=23 1u -1 - +R IE 1990 1995 - O Su>=22 1u -1 - +R IE 1996 ma - O lastSu 1u -1 - +Z Europe/Dublin -0:25:21 - LMT 1880 Au 2 -0:25:21 - DMT 1916 May 21 2s -0:25:21 1 IST 1916 O 1 2s 0 G %s 1921 D 6 @@ -1454,18 +1550,18 @@ Z Europe/Dublin -0:25 - LMT 1880 Au 2 0 - GMT 1948 Ap 18 2s 0 G GMT/IST 1968 O 27 1 IE IST/GMT -R E 1977 1980 - Ap Sun>=1 1u 1 S -R E 1977 o - S lastSun 1u 0 - +R E 1977 1980 - Ap Su>=1 1u 1 S +R E 1977 o - S lastSu 1u 0 - R E 1978 o - O 1 1u 0 - -R E 1979 1995 - S lastSun 1u 0 - -R E 1981 ma - Mar lastSun 1u 1 S -R E 1996 ma - O lastSun 1u 0 - -R W- 1977 1980 - Ap Sun>=1 1s 1 S -R W- 1977 o - S lastSun 1s 0 - +R E 1979 1995 - S lastSu 1u 0 - +R E 1981 ma - Mar lastSu 1u 1 S +R E 1996 ma - O lastSu 1u 0 - +R W- 1977 1980 - Ap Su>=1 1s 1 S +R W- 1977 o - S lastSu 1s 0 - R W- 1978 o - O 1 1s 0 - -R W- 1979 1995 - S lastSun 1s 0 - -R W- 1981 ma - Mar lastSun 1s 1 S -R W- 1996 ma - O lastSun 1s 0 - +R W- 1979 1995 - S lastSu 1s 0 - +R W- 1981 ma - Mar lastSu 1s 1 S +R W- 1996 ma - O lastSu 1s 0 - R c 1916 o - Ap 30 23 1 S R c 1916 o - O 1 1 0 - R c 1917 1918 - Ap M>=15 2s 1 S @@ -1477,18 +1573,18 @@ R c 1943 o - O 4 2s 0 - R c 1944 1945 - Ap M>=1 2s 1 S R c 1944 o - O 2 2s 0 - R c 1945 o - S 16 2s 0 - -R c 1977 1980 - Ap Sun>=1 2s 1 S -R c 1977 o - S lastSun 2s 0 - +R c 1977 1980 - Ap Su>=1 2s 1 S +R c 1977 o - S lastSu 2s 0 - R c 1978 o - O 1 2s 0 - -R c 1979 1995 - S lastSun 2s 0 - -R c 1981 ma - Mar lastSun 2s 1 S -R c 1996 ma - O lastSun 2s 0 - -R e 1977 1980 - Ap Sun>=1 0 1 S -R e 1977 o - S lastSun 0 0 - +R c 1979 1995 - S lastSu 2s 0 - +R c 1981 ma - Mar lastSu 2s 1 S +R c 1996 ma - O lastSu 2s 0 - +R e 1977 1980 - Ap Su>=1 0 1 S +R e 1977 o - S lastSu 0 0 - R e 1978 o - O 1 0 0 - -R e 1979 1995 - S lastSun 0 0 - -R e 1981 ma - Mar lastSun 0 1 S -R e 1996 ma - O lastSun 0 0 - +R e 1979 1995 - S lastSu 0 0 - +R e 1981 ma - Mar lastSu 0 1 S +R e 1996 ma - O lastSu 0 0 - R R 1917 o - Jul 1 23 1 MST R R 1917 o - D 28 0 0 MMT R R 1918 o - May 31 22 2 MDST @@ -1502,9 +1598,9 @@ R R 1921 o - S 1 0 1 MSD R R 1921 o - O 1 0 0 - R R 1981 1984 - Ap 1 0 1 S R R 1981 1983 - O 1 0 0 - -R R 1984 1995 - S lastSun 2s 0 - -R R 1985 2010 - Mar lastSun 2s 1 S -R R 1996 2010 - O lastSun 2s 0 - +R R 1984 1995 - S lastSu 2s 0 - +R R 1985 2010 - Mar lastSu 2s 1 S +R R 1996 2010 - O lastSu 2s 0 - Z WET 0 E WE%sT Z CET 1 c CE%sT Z MET 1 c ME%sT @@ -1545,7 +1641,8 @@ Z Europe/Andorra 0:6:4 - LMT 1901 R a 1920 o - Ap 5 2s 1 S R a 1920 o - S 13 2s 0 - R a 1946 o - Ap 14 2s 1 S -R a 1946 1948 - O Sun>=1 2s 0 - +R a 1946 o - O 7 2s 0 - +R a 1947 1948 - O Su>=1 2s 0 - R a 1947 o - Ap 6 2s 1 S R a 1948 o - Ap 18 2s 1 S R a 1980 o - Ap 6 0 1 S @@ -1568,21 +1665,21 @@ Z Europe/Minsk 1:50:16 - LMT 1880 2 R EE%sT 2011 Mar 27 2s 3 - +03 R b 1918 o - Mar 9 0s 1 S -R b 1918 1919 - O Sat>=1 23s 0 - +R b 1918 1919 - O Sa>=1 23s 0 - R b 1919 o - Mar 1 23s 1 S R b 1920 o - F 14 23s 1 S R b 1920 o - O 23 23s 0 - R b 1921 o - Mar 14 23s 1 S R b 1921 o - O 25 23s 0 - R b 1922 o - Mar 25 23s 1 S -R b 1922 1927 - O Sat>=1 23s 0 - +R b 1922 1927 - O Sa>=1 23s 0 - R b 1923 o - Ap 21 23s 1 S R b 1924 o - Mar 29 23s 1 S R b 1925 o - Ap 4 23s 1 S R b 1926 o - Ap 17 23s 1 S R b 1927 o - Ap 9 23s 1 S R b 1928 o - Ap 14 23s 1 S -R b 1928 1938 - O Sun>=2 2s 0 - +R b 1928 1938 - O Su>=2 2s 0 - R b 1929 o - Ap 21 2s 1 S R b 1930 o - Ap 13 2s 1 S R b 1931 o - Ap 19 2s 1 S @@ -1602,7 +1699,7 @@ R b 1945 o - S 16 2s 0 - R b 1946 o - May 19 2s 1 S R b 1946 o - O 7 2s 0 - Z Europe/Brussels 0:17:30 - LMT 1880 -0:17:30 - BMT 1892 May 1 12 +0:17:30 - BMT 1892 May 1 0:17:30 0 - WET 1914 N 8 1 - CET 1916 May 1 c CE%sT 1918 N 11 11u @@ -1612,7 +1709,7 @@ Z Europe/Brussels 0:17:30 - LMT 1880 1 E CE%sT R BG 1979 o - Mar 31 23 1 S R BG 1979 o - O 1 1 0 - -R BG 1980 1982 - Ap Sat>=1 23 1 S +R BG 1980 1982 - Ap Sa>=1 23 1 S R BG 1980 o - S 29 1 0 - R BG 1981 o - S 27 2 0 - Z Europe/Sofia 1:33:16 - LMT 1880 @@ -1628,8 +1725,8 @@ Z Europe/Sofia 1:33:16 - LMT 1880 R CZ 1945 o - Ap M>=1 2s 1 S R CZ 1945 o - O 1 2s 0 - R CZ 1946 o - May 6 2s 1 S -R CZ 1946 1949 - O Sun>=1 2s 0 - -R CZ 1947 1948 - Ap Sun>=15 2s 1 S +R CZ 1946 1949 - O Su>=1 2s 0 - +R CZ 1947 1948 - Ap Su>=15 2s 1 S R CZ 1949 o - Ap 9 2s 1 S Z Europe/Prague 0:57:44 - LMT 1850 0:57:44 - PMT 1891 O @@ -1638,32 +1735,15 @@ Z Europe/Prague 0:57:44 - LMT 1850 1 -1 GMT 1947 F 23 2 1 CZ CE%sT 1979 1 E CE%sT -R D 1916 o - May 14 23 1 S -R D 1916 o - S 30 23 0 - -R D 1940 o - May 15 0 1 S -R D 1945 o - Ap 2 2s 1 S -R D 1945 o - Au 15 2s 0 - -R D 1946 o - May 1 2s 1 S -R D 1946 o - S 1 2s 0 - -R D 1947 o - May 4 2s 1 S -R D 1947 o - Au 10 2s 0 - -R D 1948 o - May 9 2s 1 S -R D 1948 o - Au 8 2s 0 - -Z Europe/Copenhagen 0:50:20 - LMT 1890 -0:50:20 - CMT 1894 -1 D CE%sT 1942 N 2 2s -1 c CE%sT 1945 Ap 2 2 -1 D CE%sT 1980 -1 E CE%sT Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 0 - WET 1981 0 E WE%sT -R Th 1991 1992 - Mar lastSun 2 1 D -R Th 1991 1992 - S lastSun 2 0 S -R Th 1993 2006 - Ap Sun>=1 2 1 D -R Th 1993 2006 - O lastSun 2 0 S -R Th 2007 ma - Mar Sun>=8 2 1 D -R Th 2007 ma - N Sun>=1 2 0 S +R Th 1991 1992 - Mar lastSu 2 1 D +R Th 1991 1992 - S lastSu 2 0 S +R Th 1993 2006 - Ap Su>=1 2 1 D +R Th 1993 2006 - O lastSu 2 0 S +R Th 2007 ma - Mar Su>=8 2 1 D +R Th 2007 ma - N Su>=1 2 0 S Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 -3 - -03 1980 Ap 6 2 -3 E -03/-02 1996 @@ -1672,7 +1752,7 @@ Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 -2 - -02 1980 Ap 6 2 -2 c -02/-01 1981 Mar 29 -1 E -01/+00 -Z America/Godthab -3:26:56 - LMT 1916 Jul 28 +Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 -3 - -03 1980 Ap 6 2 -3 E -03/-02 Z America/Thule -4:35:8 - LMT 1916 Jul 28 @@ -1692,15 +1772,14 @@ Z Europe/Tallinn 1:39 - LMT 1880 2 E EE%sT R FI 1942 o - Ap 2 24 1 S R FI 1942 o - O 4 1 0 - -R FI 1981 1982 - Mar lastSun 2 1 S -R FI 1981 1982 - S lastSun 3 0 - +R FI 1981 1982 - Mar lastSu 2 1 S +R FI 1981 1982 - S lastSu 3 0 - Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 1:39:49 - HMT 1921 May 2 FI EE%sT 1983 2 E EE%sT -Li Europe/Helsinki Europe/Mariehamn R F 1916 o - Jun 14 23s 1 S -R F 1916 1919 - O Sun>=1 23s 0 - +R F 1916 1919 - O Su>=1 23s 0 - R F 1917 o - Mar 24 23s 1 S R F 1918 o - Mar 9 23s 1 S R F 1919 o - Mar 1 23s 1 S @@ -1709,7 +1788,7 @@ R F 1920 o - O 23 23s 0 - R F 1921 o - Mar 14 23s 1 S R F 1921 o - O 25 23s 0 - R F 1922 o - Mar 25 23s 1 S -R F 1922 1938 - O Sat>=1 23s 0 - +R F 1922 1938 - O Sa>=1 23s 0 - R F 1923 o - May 26 23s 1 S R F 1924 o - Mar 29 23s 1 S R F 1925 o - Ap 4 23s 1 S @@ -1741,8 +1820,8 @@ R F 1945 o - Ap 2 2 2 M R F 1945 o - S 16 3 0 - R F 1976 o - Mar 28 1 1 S R F 1976 o - S 26 1 0 - -Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 -0:9:21 - PMT 1911 Mar 11 0:1 +Z Europe/Paris 0:9:21 - LMT 1891 Mar 16 +0:9:21 - PMT 1911 Mar 11 0 F WE%sT 1940 Jun 14 23 1 c CE%sT 1944 Au 25 0 F WE%sT 1945 S 16 3 @@ -1750,7 +1829,7 @@ Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 1 E CE%sT R DE 1946 o - Ap 14 2s 1 S R DE 1946 o - O 7 2s 0 - -R DE 1947 1949 - O Sun>=1 2s 0 - +R DE 1947 1949 - O Su>=1 2s 0 - R DE 1947 o - Ap 6 3s 1 S R DE 1947 o - May 11 2s 2 M R DE 1947 o - Jun 29 3 1 S @@ -1764,8 +1843,7 @@ Z Europe/Berlin 0:53:28 - LMT 1893 Ap 1 So CE%sT 1946 1 DE CE%sT 1980 1 E CE%sT -Li Europe/Zurich Europe/Busingen -Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s +Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0 G %s 1957 Ap 14 2 1 - CET 1982 1 E CE%sT @@ -1781,7 +1859,7 @@ R g 1975 o - Ap 12 0s 1 S R g 1975 o - N 26 0s 0 - R g 1976 o - Ap 11 2s 1 S R g 1976 o - O 10 2s 0 - -R g 1977 1978 - Ap Sun>=1 2s 1 S +R g 1977 1978 - Ap Su>=1 2s 1 S R g 1977 o - S 26 2s 0 - R g 1978 o - S 24 4 0 - R g 1979 o - Ap 1 9 1 S @@ -1794,49 +1872,31 @@ Z Europe/Athens 1:34:52 - LMT 1895 S 14 1 g CE%sT 1944 Ap 4 2 g EE%sT 1981 2 E EE%sT -R h 1918 o - Ap 1 3 1 S -R h 1918 o - S 16 3 0 - -R h 1919 o - Ap 15 3 1 S -R h 1919 o - N 24 3 0 - +R h 1918 1919 - Ap 15 2 1 S +R h 1918 1920 - S M>=15 3 0 - +R h 1920 o - Ap 5 2 1 S R h 1945 o - May 1 23 1 S -R h 1945 o - N 1 0 0 - +R h 1945 o - N 1 1 0 - R h 1946 o - Mar 31 2s 1 S -R h 1946 1949 - O Sun>=1 2s 0 - -R h 1947 1949 - Ap Sun>=4 2s 1 S -R h 1950 o - Ap 17 2s 1 S -R h 1950 o - O 23 2s 0 - -R h 1954 1955 - May 23 0 1 S -R h 1954 1955 - O 3 0 0 - -R h 1956 o - Jun Sun>=1 0 1 S -R h 1956 o - S lastSun 0 0 - -R h 1957 o - Jun Sun>=1 1 1 S -R h 1957 o - S lastSun 3 0 - -R h 1980 o - Ap 6 1 1 S -Z Europe/Budapest 1:16:20 - LMT 1890 O +R h 1946 o - O 7 2 0 - +R h 1947 1949 - Ap Su>=4 2s 1 S +R h 1947 1949 - O Su>=1 2s 0 - +R h 1954 o - May 23 0 1 S +R h 1954 o - O 3 0 0 - +R h 1955 o - May 22 2 1 S +R h 1955 o - O 2 3 0 - +R h 1956 1957 - Jun Su>=1 2 1 S +R h 1956 1957 - S lastSu 3 0 - +R h 1980 o - Ap 6 0 1 S +R h 1980 o - S 28 1 0 - +R h 1981 1983 - Mar lastSu 0 1 S +R h 1981 1983 - S lastSu 1 0 - +Z Europe/Budapest 1:16:20 - LMT 1890 N 1 c CE%sT 1918 -1 h CE%sT 1941 Ap 8 +1 h CE%sT 1941 Ap 7 23 1 c CE%sT 1945 -1 h CE%sT 1980 S 28 2s +1 h CE%sT 1984 1 E CE%sT -R w 1917 1919 - F 19 23 1 - -R w 1917 o - O 21 1 0 - -R w 1918 1919 - N 16 1 0 - -R w 1921 o - Mar 19 23 1 - -R w 1921 o - Jun 23 1 0 - -R w 1939 o - Ap 29 23 1 - -R w 1939 o - O 29 2 0 - -R w 1940 o - F 25 2 1 - -R w 1940 1941 - N Sun>=2 1s 0 - -R w 1941 1942 - Mar Sun>=2 1s 1 - -R w 1943 1946 - Mar Sun>=1 1s 1 - -R w 1942 1948 - O Sun>=22 1s 0 - -R w 1947 1967 - Ap Sun>=1 1s 1 - -R w 1949 o - O 30 1s 0 - -R w 1950 1966 - O Sun>=22 1s 0 - -R w 1967 o - O 29 1s 0 - -Z Atlantic/Reykjavik -1:28 - LMT 1908 --1 w -01/+00 1968 Ap 7 1s -0 - GMT R I 1916 o - Jun 3 24 1 S R I 1916 1917 - S 30 24 0 - R I 1917 o - Mar 31 24 1 S @@ -1860,34 +1920,32 @@ R I 1947 o - Mar 16 0s 1 S R I 1947 o - O 5 0s 0 - R I 1948 o - F 29 2s 1 S R I 1948 o - O 3 2s 0 - -R I 1966 1968 - May Sun>=22 0s 1 S +R I 1966 1968 - May Su>=22 0s 1 S R I 1966 o - S 24 24 0 - -R I 1967 1969 - S Sun>=22 0s 0 - +R I 1967 1969 - S Su>=22 0s 0 - R I 1969 o - Jun 1 0s 1 S R I 1970 o - May 31 0s 1 S -R I 1970 o - S lastSun 0s 0 - -R I 1971 1972 - May Sun>=22 0s 1 S -R I 1971 o - S lastSun 0s 0 - +R I 1970 o - S lastSu 0s 0 - +R I 1971 1972 - May Su>=22 0s 1 S +R I 1971 o - S lastSu 0s 0 - R I 1972 o - O 1 0s 0 - R I 1973 o - Jun 3 0s 1 S -R I 1973 1974 - S lastSun 0s 0 - +R I 1973 1974 - S lastSu 0s 0 - R I 1974 o - May 26 0s 1 S R I 1975 o - Jun 1 0s 1 S -R I 1975 1977 - S lastSun 0s 0 - +R I 1975 1977 - S lastSu 0s 0 - R I 1976 o - May 30 0s 1 S -R I 1977 1979 - May Sun>=22 0s 1 S +R I 1977 1979 - May Su>=22 0s 1 S R I 1978 o - O 1 0s 0 - R I 1979 o - S 30 0s 0 - -Z Europe/Rome 0:49:56 - LMT 1866 S 22 -0:49:56 - RMT 1893 O 31 23:49:56 +Z Europe/Rome 0:49:56 - LMT 1866 D 12 +0:49:56 - RMT 1893 O 31 23u 1 I CE%sT 1943 S 10 1 c CE%sT 1944 Jun 4 1 I CE%sT 1980 1 E CE%sT -Li Europe/Rome Europe/Vatican -Li Europe/Rome Europe/San_Marino -R LV 1989 1996 - Mar lastSun 2s 1 S -R LV 1989 1996 - S lastSun 2s 0 - +R LV 1989 1996 - Mar lastSu 2s 1 S +R LV 1989 1996 - S lastSu 2s 0 - Z Europe/Riga 1:36:34 - LMT 1880 1:36:34 - RMT 1918 Ap 15 2 1:36:34 1 LST 1918 S 16 3 @@ -1897,13 +1955,12 @@ Z Europe/Riga 1:36:34 - LMT 1880 2 - EET 1940 Au 5 3 - MSK 1941 Jul 1 c CE%sT 1944 O 13 -3 R MSK/MSD 1989 Mar lastSun 2s -2 1 EEST 1989 S lastSun 2s +3 R MSK/MSD 1989 Mar lastSu 2s +2 1 EEST 1989 S lastSu 2s 2 LV EE%sT 1997 Ja 21 2 E EE%sT 2000 F 29 2 - EET 2001 Ja 2 2 E EE%sT -Li Europe/Zurich Europe/Vaduz Z Europe/Vilnius 1:41:16 - LMT 1880 1:24 - WMT 1917 1:35:36 - KMT 1919 O 10 @@ -1919,49 +1976,19 @@ Z Europe/Vilnius 1:41:16 - LMT 1880 1 E CE%sT 1999 O 31 1u 2 - EET 2003 2 E EE%sT -R LX 1916 o - May 14 23 1 S -R LX 1916 o - O 1 1 0 - -R LX 1917 o - Ap 28 23 1 S -R LX 1917 o - S 17 1 0 - -R LX 1918 o - Ap M>=15 2s 1 S -R LX 1918 o - S M>=15 2s 0 - -R LX 1919 o - Mar 1 23 1 S -R LX 1919 o - O 5 3 0 - -R LX 1920 o - F 14 23 1 S -R LX 1920 o - O 24 2 0 - -R LX 1921 o - Mar 14 23 1 S -R LX 1921 o - O 26 2 0 - -R LX 1922 o - Mar 25 23 1 S -R LX 1922 o - O Sun>=2 1 0 - -R LX 1923 o - Ap 21 23 1 S -R LX 1923 o - O Sun>=2 2 0 - -R LX 1924 o - Mar 29 23 1 S -R LX 1924 1928 - O Sun>=2 1 0 - -R LX 1925 o - Ap 5 23 1 S -R LX 1926 o - Ap 17 23 1 S -R LX 1927 o - Ap 9 23 1 S -R LX 1928 o - Ap 14 23 1 S -R LX 1929 o - Ap 20 23 1 S -Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun -1 LX CE%sT 1918 N 25 -0 LX WE%sT 1929 O 6 2s -0 b WE%sT 1940 May 14 3 -1 c WE%sT 1944 S 18 3 -1 b CE%sT 1977 -1 E CE%sT R MT 1973 o - Mar 31 0s 1 S R MT 1973 o - S 29 0s 0 - R MT 1974 o - Ap 21 0s 1 S R MT 1974 o - S 16 0s 0 - -R MT 1975 1979 - Ap Sun>=15 2 1 S -R MT 1975 1980 - S Sun>=15 2 0 - +R MT 1975 1979 - Ap Su>=15 2 1 S +R MT 1975 1980 - S Su>=15 2 0 - R MT 1980 o - Mar 31 2 1 S -Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s +Z Europe/Malta 0:58:4 - LMT 1893 N 2 1 I CE%sT 1973 Mar 31 1 MT CE%sT 1981 1 E CE%sT -R MD 1997 ma - Mar lastSun 2 1 S -R MD 1997 ma - O lastSun 3 0 - +R MD 1997 ma - Mar lastSu 2 1 S +R MD 1997 ma - O lastSu 3 0 - Z Europe/Chisinau 1:55:20 - LMT 1880 1:55 - CMT 1918 F 15 1:44:24 - BMT 1931 Jul 24 @@ -1972,50 +1999,6 @@ Z Europe/Chisinau 1:55:20 - LMT 1880 2 R EE%sT 1992 2 e EE%sT 1997 2 MD EE%sT -Z Europe/Monaco 0:29:32 - LMT 1891 Mar 15 -0:9:21 - PMT 1911 Mar 11 -0 F WE%sT 1945 S 16 3 -1 F CE%sT 1977 -1 E CE%sT -R N 1916 o - May 1 0 1 NST -R N 1916 o - O 1 0 0 AMT -R N 1917 o - Ap 16 2s 1 NST -R N 1917 o - S 17 2s 0 AMT -R N 1918 1921 - Ap M>=1 2s 1 NST -R N 1918 1921 - S lastM 2s 0 AMT -R N 1922 o - Mar lastSun 2s 1 NST -R N 1922 1936 - O Sun>=2 2s 0 AMT -R N 1923 o - Jun F>=1 2s 1 NST -R N 1924 o - Mar lastSun 2s 1 NST -R N 1925 o - Jun F>=1 2s 1 NST -R N 1926 1931 - May 15 2s 1 NST -R N 1932 o - May 22 2s 1 NST -R N 1933 1936 - May 15 2s 1 NST -R N 1937 o - May 22 2s 1 NST -R N 1937 o - Jul 1 0 1 S -R N 1937 1939 - O Sun>=2 2s 0 - -R N 1938 1939 - May 15 2s 1 S -R N 1945 o - Ap 2 2s 1 S -R N 1945 o - S 16 2s 0 - -Z Europe/Amsterdam 0:19:32 - LMT 1835 -0:19:32 N %s 1937 Jul -0:20 N +0020/+0120 1940 May 16 -1 c CE%sT 1945 Ap 2 2 -1 N CE%sT 1977 -1 E CE%sT -R NO 1916 o - May 22 1 1 S -R NO 1916 o - S 30 0 0 - -R NO 1945 o - Ap 2 2s 1 S -R NO 1945 o - O 1 2s 0 - -R NO 1959 1964 - Mar Sun>=15 2s 1 S -R NO 1959 1965 - S Sun>=15 2s 0 - -R NO 1965 o - Ap 25 2s 1 S -Z Europe/Oslo 0:43 - LMT 1895 -1 NO CE%sT 1940 Au 10 23 -1 c CE%sT 1945 Ap 2 2 -1 NO CE%sT 1980 -1 E CE%sT -Li Europe/Oslo Arctic/Longyearbyen R O 1918 1919 - S 16 2s 0 - R O 1919 o - Ap 15 2s 1 S R O 1944 o - Ap 3 2s 1 S @@ -2025,17 +2008,17 @@ R O 1945 o - N 1 0 0 - R O 1946 o - Ap 14 0s 1 S R O 1946 o - O 7 2s 0 - R O 1947 o - May 4 2s 1 S -R O 1947 1949 - O Sun>=1 2s 0 - +R O 1947 1949 - O Su>=1 2s 0 - R O 1948 o - Ap 18 2s 1 S R O 1949 o - Ap 10 2s 1 S R O 1957 o - Jun 2 1s 1 S -R O 1957 1958 - S lastSun 1s 0 - +R O 1957 1958 - S lastSu 1s 0 - R O 1958 o - Mar 30 1s 1 S R O 1959 o - May 31 1s 1 S -R O 1959 1961 - O Sun>=1 1s 0 - +R O 1959 1961 - O Su>=1 1s 0 - R O 1960 o - Ap 3 1s 1 S -R O 1961 1964 - May lastSun 1s 1 S -R O 1962 1964 - S lastSun 1s 0 - +R O 1961 1964 - May lastSu 1s 1 S +R O 1962 1964 - S lastSu 1s 0 - Z Europe/Warsaw 1:24 - LMT 1880 1:24 - WMT 1915 Au 5 1 c CE%sT 1918 S 16 3 @@ -2056,15 +2039,15 @@ R p 1921 o - F 28 23s 1 S R p 1924 o - Ap 16 23s 1 S R p 1924 o - O 14 23s 0 - R p 1926 o - Ap 17 23s 1 S -R p 1926 1929 - O Sat>=1 23s 0 - +R p 1926 1929 - O Sa>=1 23s 0 - R p 1927 o - Ap 9 23s 1 S R p 1928 o - Ap 14 23s 1 S R p 1929 o - Ap 20 23s 1 S R p 1931 o - Ap 18 23s 1 S -R p 1931 1932 - O Sat>=1 23s 0 - +R p 1931 1932 - O Sa>=1 23s 0 - R p 1932 o - Ap 2 23s 1 S R p 1934 o - Ap 7 23s 1 S -R p 1934 1938 - O Sat>=1 23s 0 - +R p 1934 1938 - O Sa>=1 23s 0 - R p 1935 o - Mar 30 23s 1 S R p 1936 o - Ap 18 23s 1 S R p 1937 o - Ap 3 23s 1 S @@ -2074,27 +2057,25 @@ R p 1939 o - N 18 23s 0 - R p 1940 o - F 24 23s 1 S R p 1940 1941 - O 5 23s 0 - R p 1941 o - Ap 5 23s 1 S -R p 1942 1945 - Mar Sat>=8 23s 1 S +R p 1942 1945 - Mar Sa>=8 23s 1 S R p 1942 o - Ap 25 22s 2 M R p 1942 o - Au 15 22s 1 S -R p 1942 1945 - O Sat>=24 23s 0 - +R p 1942 1945 - O Sa>=24 23s 0 - R p 1943 o - Ap 17 22s 2 M -R p 1943 1945 - Au Sat>=25 22s 1 S -R p 1944 1945 - Ap Sat>=21 22s 2 M -R p 1946 o - Ap Sat>=1 23s 1 S -R p 1946 o - O Sat>=1 23s 0 - -R p 1947 1949 - Ap Sun>=1 2s 1 S -R p 1947 1949 - O Sun>=1 2s 0 - -R p 1951 1965 - Ap Sun>=1 2s 1 S -R p 1951 1965 - O Sun>=1 2s 0 - +R p 1943 1945 - Au Sa>=25 22s 1 S +R p 1944 1945 - Ap Sa>=21 22s 2 M +R p 1946 o - Ap Sa>=1 23s 1 S +R p 1946 o - O Sa>=1 23s 0 - +R p 1947 1965 - Ap Su>=1 2s 1 S +R p 1947 1965 - O Su>=1 2s 0 - R p 1977 o - Mar 27 0s 1 S R p 1977 o - S 25 0s 0 - -R p 1978 1979 - Ap Sun>=1 0s 1 S +R p 1978 1979 - Ap Su>=1 0s 1 S R p 1978 o - O 1 0s 0 - -R p 1979 1982 - S lastSun 1s 0 - -R p 1980 o - Mar lastSun 0s 1 S -R p 1981 1982 - Mar lastSun 1s 1 S -R p 1983 o - Mar lastSun 2s 1 S +R p 1979 1982 - S lastSu 1s 0 - +R p 1980 o - Mar lastSu 0s 1 S +R p 1981 1982 - Mar lastSu 1s 1 S +R p 1983 o - Mar lastSu 2s 1 S Z Europe/Lisbon -0:36:45 - LMT 1884 -0:36:45 - LMT 1912 Ja 1 0u 0 p WE%sT 1966 Ap 3 2 @@ -2132,14 +2113,14 @@ Z Atlantic/Madeira -1:7:36 - LMT 1884 0 p WE%sT 1983 S 25 1s 0 E WE%sT R z 1932 o - May 21 0s 1 S -R z 1932 1939 - O Sun>=1 0s 0 - -R z 1933 1939 - Ap Sun>=2 0s 1 S +R z 1932 1939 - O Su>=1 0s 0 - +R z 1933 1939 - Ap Su>=2 0s 1 S R z 1979 o - May 27 0 1 S -R z 1979 o - S lastSun 0 0 - +R z 1979 o - S lastSu 0 0 - R z 1980 o - Ap 5 23 1 S -R z 1980 o - S lastSun 1 0 - -R z 1991 1993 - Mar lastSun 0s 1 S -R z 1991 1993 - S lastSun 0s 0 - +R z 1980 o - S lastSu 1 0 - +R z 1991 1993 - Mar lastSu 0s 1 S +R z 1991 1993 - S lastSu 0s 0 - Z Europe/Bucharest 1:44:24 - LMT 1891 O 1:44:24 - BMT 1931 Jul 24 2 z EE%sT 1981 Mar 29 2s @@ -2148,8 +2129,8 @@ Z Europe/Bucharest 1:44:24 - LMT 1891 O 2 e EE%sT 1997 2 E EE%sT Z Europe/Kaliningrad 1:22 - LMT 1893 Ap -1 c CE%sT 1945 -2 O CE%sT 1946 +1 c CE%sT 1945 Ap 10 +2 O EE%sT 1946 Ap 7 3 R MSK/MSD 1989 Mar 26 2s 2 R EE%sT 2011 Mar 27 2s 3 - +03 2014 O 26 2s @@ -2172,12 +2153,11 @@ Z Europe/Simferopol 2:16:24 - LMT 1880 1 c CE%sT 1944 Ap 13 3 R MSK/MSD 1990 3 - MSK 1990 Jul 1 2 -2 - EET 1992 -2 e EE%sT 1994 May -3 e MSK/MSD 1996 Mar 31 0s +2 - EET 1992 Mar 20 +2 c EE%sT 1994 May +3 c MSK/MSD 1996 Mar 31 0s 3 1 MSD 1996 O 27 3s -3 R MSK/MSD 1997 -3 - MSK 1997 Mar lastSun 1u +3 - MSK 1997 Mar lastSu 1u 2 E EE%sT 2014 Mar 30 2 4 - MSK 2014 O 26 2s 3 - MSK @@ -2199,7 +2179,8 @@ Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 3 R +03/+04 2011 Mar 27 2s 4 - +04 2014 O 26 2s 3 - +03 2018 O 28 2s -4 - +04 +4 - +04 2020 D 27 2s +3 - +03 Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 R +04/+05 1988 Mar 27 2s @@ -2335,7 +2316,7 @@ Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 9 - +09 1945 Au 25 11 R +11/+12 1991 Mar 31 2s 10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 1997 Mar lastSun 2s +11 R +11/+12 1997 Mar lastSu 2s 10 R +10/+11 2011 Mar 27 2s 11 - +11 2014 O 26 2s 10 - +10 2016 Mar 27 2s @@ -2386,19 +2367,13 @@ Z Europe/Belgrade 1:22 - LMT 1884 1 1 CEST 1945 S 16 2s 1 - CET 1982 N 27 1 E CE%sT -Li Europe/Belgrade Europe/Ljubljana -Li Europe/Belgrade Europe/Podgorica -Li Europe/Belgrade Europe/Sarajevo -Li Europe/Belgrade Europe/Skopje -Li Europe/Belgrade Europe/Zagreb -Li Europe/Prague Europe/Bratislava R s 1918 o - Ap 15 23 1 S R s 1918 1919 - O 6 24s 0 - R s 1919 o - Ap 6 23 1 S R s 1924 o - Ap 16 23 1 S R s 1924 o - O 4 24s 0 - R s 1926 o - Ap 17 23 1 S -R s 1926 1929 - O Sat>=1 24s 0 - +R s 1926 1929 - O Sa>=1 24s 0 - R s 1927 o - Ap 9 23 1 S R s 1928 o - Ap 15 0 1 S R s 1929 o - Ap 20 23 1 S @@ -2410,15 +2385,15 @@ R s 1938 o - O 2 24 1 S R s 1939 o - O 7 24s 0 - R s 1942 o - May 2 23 1 S R s 1942 o - S 1 1 0 - -R s 1943 1946 - Ap Sat>=13 23 1 S -R s 1943 1944 - O Sun>=1 1 0 - -R s 1945 1946 - S lastSun 1 0 - +R s 1943 1946 - Ap Sa>=13 23 1 S +R s 1943 1944 - O Su>=1 1 0 - +R s 1945 1946 - S lastSu 1 0 - R s 1949 o - Ap 30 23 1 S R s 1949 o - O 2 1 0 - -R s 1974 1975 - Ap Sat>=12 23 1 S -R s 1974 1975 - O Sun>=1 1 0 - +R s 1974 1975 - Ap Sa>=12 23 1 S +R s 1974 1975 - O Su>=1 1 0 - R s 1976 o - Mar 27 23 1 S -R s 1976 1977 - S lastSun 1 0 - +R s 1976 1977 - S lastSu 1 0 - R s 1977 o - Ap 2 23 1 S R s 1978 o - Ap 2 2s 1 S R s 1978 o - O 1 2s 0 - @@ -2431,11 +2406,11 @@ R Sp 1976 o - Au 1 0 0 - R Sp 1977 o - S 28 0 0 - R Sp 1978 o - Jun 1 0 1 S R Sp 1978 o - Au 4 0 0 - -Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 +Z Europe/Madrid -0:14:44 - LMT 1901 Ja 1 0u 0 s WE%sT 1940 Mar 16 23 1 s CE%sT 1979 1 E CE%sT -Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 +Z Africa/Ceuta -0:21:16 - LMT 1901 Ja 1 0u 0 - WET 1918 May 6 23 0 1 WEST 1918 O 7 23 0 - WET 1924 @@ -2449,12 +2424,6 @@ Z Atlantic/Canary -1:1:36 - LMT 1922 Mar 0 - WET 1980 Ap 6 0s 0 1 WEST 1980 S 28 1u 0 E WE%sT -Z Europe/Stockholm 1:12:12 - LMT 1879 -1:0:14 - SET 1900 -1 - CET 1916 May 14 23 -1 1 CEST 1916 O 1 1 -1 - CET 1980 -1 E CE%sT R CH 1941 1942 - May M>=1 1 1 S R CH 1941 1942 - O M>=1 2 0 - Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 @@ -2472,53 +2441,44 @@ R T 1922 o - O 8 0 0 - R T 1924 o - May 13 0 1 S R T 1924 1925 - O 1 0 0 - R T 1925 o - May 1 0 1 S -R T 1940 o - Jun 30 0 1 S -R T 1940 o - O 5 0 0 - +R T 1940 o - Jul 1 0 1 S +R T 1940 o - O 6 0 0 - R T 1940 o - D 1 0 1 S R T 1941 o - S 21 0 0 - R T 1942 o - Ap 1 0 1 S -R T 1942 o - N 1 0 0 - -R T 1945 o - Ap 2 0 1 S R T 1945 o - O 8 0 0 - R T 1946 o - Jun 1 0 1 S R T 1946 o - O 1 0 0 - -R T 1947 1948 - Ap Sun>=16 0 1 S -R T 1947 1950 - O Sun>=2 0 0 - +R T 1947 1948 - Ap Su>=16 0 1 S +R T 1947 1951 - O Su>=2 0 0 - R T 1949 o - Ap 10 0 1 S -R T 1950 o - Ap 19 0 1 S +R T 1950 o - Ap 16 0 1 S R T 1951 o - Ap 22 0 1 S -R T 1951 o - O 8 0 0 - R T 1962 o - Jul 15 0 1 S -R T 1962 o - O 8 0 0 - +R T 1963 o - O 30 0 0 - R T 1964 o - May 15 0 1 S R T 1964 o - O 1 0 0 - -R T 1970 1972 - May Sun>=2 0 1 S -R T 1970 1972 - O Sun>=2 0 0 - R T 1973 o - Jun 3 1 1 S -R T 1973 o - N 4 3 0 - +R T 1973 1976 - O Su>=31 2 0 - R T 1974 o - Mar 31 2 1 S -R T 1974 o - N 3 5 0 - -R T 1975 o - Mar 30 0 1 S -R T 1975 1976 - O lastSun 0 0 - -R T 1976 o - Jun 1 0 1 S -R T 1977 1978 - Ap Sun>=1 0 1 S -R T 1977 o - O 16 0 0 - -R T 1979 1980 - Ap Sun>=1 3 1 S -R T 1979 1982 - O M>=11 0 0 - -R T 1981 1982 - Mar lastSun 3 1 S -R T 1983 o - Jul 31 0 1 S -R T 1983 o - O 2 0 0 - -R T 1985 o - Ap 20 0 1 S -R T 1985 o - S 28 0 0 - -R T 1986 1993 - Mar lastSun 1s 1 S -R T 1986 1995 - S lastSun 1s 0 - +R T 1975 o - Mar 22 2 1 S +R T 1976 o - Mar 21 2 1 S +R T 1977 1978 - Ap Su>=1 2 1 S +R T 1977 1978 - O Su>=15 2 0 - +R T 1978 o - Jun 29 0 0 - +R T 1983 o - Jul 31 2 1 S +R T 1983 o - O 2 2 0 - +R T 1985 o - Ap 20 1s 1 S +R T 1985 o - S 28 1s 0 - +R T 1986 1993 - Mar lastSu 1s 1 S +R T 1986 1995 - S lastSu 1s 0 - R T 1994 o - Mar 20 1s 1 S -R T 1995 2006 - Mar lastSun 1s 1 S -R T 1996 2006 - O lastSun 1s 0 - +R T 1995 2006 - Mar lastSu 1s 1 S +R T 1996 2006 - O lastSu 1s 0 - Z Europe/Istanbul 1:55:52 - LMT 1880 1:56:56 - IMT 1910 O -2 T EE%sT 1978 O 15 -3 T +03/+04 1985 Ap 20 +2 T EE%sT 1978 Jun 29 +3 T +03/+04 1984 N 1 2 2 T EE%sT 2007 2 E EE%sT 2011 Mar 27 1u 2 - EET 2011 Mar 28 1u @@ -2528,48 +2488,28 @@ Z Europe/Istanbul 1:55:52 - LMT 1880 2 1 EEST 2015 N 8 1u 2 E EE%sT 2016 S 7 3 - +03 -Li Europe/Istanbul Asia/Istanbul -Z Europe/Kiev 2:2:4 - LMT 1880 +Z Europe/Kyiv 2:2:4 - LMT 1880 2:2:4 - KMT 1924 May 2 2 - EET 1930 Jun 21 3 - MSK 1941 S 20 1 c CE%sT 1943 N 6 3 R MSK/MSD 1990 Jul 1 2 2 1 EEST 1991 S 29 3 -2 e EE%sT 1995 +2 c EE%sT 1996 May 13 2 E EE%sT -Z Europe/Uzhgorod 1:29:12 - LMT 1890 O -1 - CET 1940 -1 c CE%sT 1944 O -1 1 CEST 1944 O 26 -1 - CET 1945 Jun 29 -3 R MSK/MSD 1990 -3 - MSK 1990 Jul 1 2 -1 - CET 1991 Mar 31 3 -2 - EET 1992 -2 e EE%sT 1995 -2 E EE%sT -Z Europe/Zaporozhye 2:20:40 - LMT 1880 -2:20 - +0220 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 Au 25 -1 c CE%sT 1943 O 25 -3 R MSK/MSD 1991 Mar 31 2 -2 e EE%sT 1995 -2 E EE%sT -R u 1918 1919 - Mar lastSun 2 1 D -R u 1918 1919 - O lastSun 2 0 S +R u 1918 1919 - Mar lastSu 2 1 D +R u 1918 1919 - O lastSu 2 0 S R u 1942 o - F 9 2 1 W R u 1945 o - Au 14 23u 1 P -R u 1945 o - S lastSun 2 0 S -R u 1967 2006 - O lastSun 2 0 S -R u 1967 1973 - Ap lastSun 2 1 D +R u 1945 o - S 30 2 0 S +R u 1967 2006 - O lastSu 2 0 S +R u 1967 1973 - Ap lastSu 2 1 D R u 1974 o - Ja 6 2 1 D -R u 1975 o - F 23 2 1 D -R u 1976 1986 - Ap lastSun 2 1 D -R u 1987 2006 - Ap Sun>=1 2 1 D -R u 2007 ma - Mar Sun>=8 2 1 D -R u 2007 ma - N Sun>=1 2 0 S +R u 1975 o - F lastSu 2 1 D +R u 1976 1986 - Ap lastSu 2 1 D +R u 1987 2006 - Ap Su>=1 2 1 D +R u 2007 ma - Mar Su>=8 2 1 D +R u 2007 ma - N Su>=1 2 0 S Z EST -5 - EST Z MST -7 - MST Z HST -10 - HST @@ -2577,24 +2517,24 @@ Z EST5EDT -5 u E%sT Z CST6CDT -6 u C%sT Z MST7MDT -7 u M%sT Z PST8PDT -8 u P%sT -R NY 1920 o - Mar lastSun 2 1 D -R NY 1920 o - O lastSun 2 0 S -R NY 1921 1966 - Ap lastSun 2 1 D -R NY 1921 1954 - S lastSun 2 0 S -R NY 1955 1966 - O lastSun 2 0 S -Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 +R NY 1920 o - Mar lastSu 2 1 D +R NY 1920 o - O lastSu 2 0 S +R NY 1921 1966 - Ap lastSu 2 1 D +R NY 1921 1954 - S lastSu 2 0 S +R NY 1955 1966 - O lastSu 2 0 S +Z America/New_York -4:56:2 - LMT 1883 N 18 17u -5 u E%sT 1920 -5 NY E%sT 1942 -5 u E%sT 1946 -5 NY E%sT 1967 -5 u E%sT R Ch 1920 o - Jun 13 2 1 D -R Ch 1920 1921 - O lastSun 2 0 S -R Ch 1921 o - Mar lastSun 2 1 D -R Ch 1922 1966 - Ap lastSun 2 1 D -R Ch 1922 1954 - S lastSun 2 0 S -R Ch 1955 1966 - O lastSun 2 0 S -Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 +R Ch 1920 1921 - O lastSu 2 0 S +R Ch 1921 o - Mar lastSu 2 1 D +R Ch 1922 1966 - Ap lastSu 2 1 D +R Ch 1922 1954 - S lastSu 2 0 S +R Ch 1955 1966 - O lastSu 2 0 S +Z America/Chicago -5:50:36 - LMT 1883 N 18 18u -6 u C%sT 1920 -6 Ch C%sT 1936 Mar 1 2 -5 - EST 1936 N 15 2 @@ -2602,21 +2542,21 @@ Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 -6 u C%sT 1946 -6 Ch C%sT 1967 -6 u C%sT -Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 +Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 19u -7 u M%sT 1992 O 25 2 -6 u C%sT -Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 +Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 19u -7 u M%sT 2003 O 26 2 -6 u C%sT -Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 +Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 19u -7 u M%sT 2010 N 7 2 -6 u C%sT -R De 1920 1921 - Mar lastSun 2 1 D -R De 1920 o - O lastSun 2 0 S +R De 1920 1921 - Mar lastSu 2 1 D +R De 1920 o - O lastSu 2 0 S R De 1921 o - May 22 2 0 S -R De 1965 1966 - Ap lastSun 2 1 D -R De 1965 1966 - O lastSun 2 0 S -Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 +R De 1965 1966 - Ap lastSu 2 1 D +R De 1965 1966 - O lastSu 2 0 S +Z America/Denver -6:59:56 - LMT 1883 N 18 19u -7 u M%sT 1920 -7 De M%sT 1942 -7 u M%sT 1946 @@ -2624,10 +2564,10 @@ Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 -7 u M%sT R CA 1948 o - Mar 14 2:1 1 D R CA 1949 o - Ja 1 2 0 S -R CA 1950 1966 - Ap lastSun 1 1 D -R CA 1950 1961 - S lastSun 2 0 S -R CA 1962 1966 - O lastSun 2 0 S -Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 +R CA 1950 1966 - Ap lastSu 1 1 D +R CA 1950 1961 - S lastSu 2 0 S +R CA 1962 1966 - O lastSu 2 0 S +Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 20u -8 u P%sT 1946 -8 CA P%sT 1967 -8 u P%sT @@ -2656,6 +2596,8 @@ Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 -8 - PST 1969 -8 u P%sT 1983 O 30 2 -8 - PST 2015 N 1 2 +-9 u AK%sT 2018 N 4 2 +-8 - PST 2019 Ja 20 2 -9 u AK%sT Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 -9:18:55 - LMT 1900 Au 20 12 @@ -2695,22 +2637,22 @@ Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 -10:30 1 HDT 1933 May 21 12 -10:30 u H%sT 1947 Jun 8 2 -10 - HST -Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 +Z America/Phoenix -7:28:18 - LMT 1883 N 18 19u -7 u M%sT 1944 Ja 1 0:1 -7 - MST 1944 Ap 1 0:1 -7 u M%sT 1944 O 1 0:1 -7 - MST 1967 -7 u M%sT 1968 Mar 21 -7 - MST -Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 +Z America/Boise -7:44:49 - LMT 1883 N 18 20u -8 u P%sT 1923 May 13 2 -7 u M%sT 1974 -7 - MST 1974 F 3 2 -7 u M%sT R In 1941 o - Jun 22 2 1 D -R In 1941 1954 - S lastSun 2 0 S -R In 1946 1954 - Ap lastSun 2 1 D -Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 +R In 1941 1954 - S lastSu 2 0 S +R In 1946 1954 - Ap lastSu 2 1 D +Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 18u -6 u C%sT 1920 -6 In C%sT 1942 -6 u C%sT 1946 @@ -2721,11 +2663,11 @@ Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 -5 u E%sT 1971 -5 - EST 2006 -5 u E%sT -R Ma 1951 o - Ap lastSun 2 1 D -R Ma 1951 o - S lastSun 2 0 S -R Ma 1954 1960 - Ap lastSun 2 1 D -R Ma 1954 1960 - S lastSun 2 0 S -Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 +R Ma 1951 o - Ap lastSu 2 1 D +R Ma 1951 o - S lastSu 2 0 S +R Ma 1954 1960 - Ap lastSu 2 1 D +R Ma 1954 1960 - S lastSu 2 0 S +Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 18u -6 u C%sT 1951 -6 Ma C%sT 1961 Ap 30 2 -5 - EST 1969 @@ -2734,16 +2676,16 @@ Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 -5 u E%sT 1976 -5 - EST 2006 -5 u E%sT -R V 1946 o - Ap lastSun 2 1 D -R V 1946 o - S lastSun 2 0 S -R V 1953 1954 - Ap lastSun 2 1 D -R V 1953 1959 - S lastSun 2 0 S +R V 1946 o - Ap lastSu 2 1 D +R V 1946 o - S lastSu 2 0 S +R V 1953 1954 - Ap lastSu 2 1 D +R V 1953 1959 - S lastSu 2 0 S R V 1955 o - May 1 0 1 D -R V 1956 1963 - Ap lastSun 2 1 D -R V 1960 o - O lastSun 2 0 S -R V 1961 o - S lastSun 2 0 S -R V 1962 1963 - O lastSun 2 0 S -Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 +R V 1956 1963 - Ap lastSu 2 1 D +R V 1960 o - O lastSu 2 0 S +R V 1961 o - S lastSu 2 0 S +R V 1962 1963 - O lastSu 2 0 S +Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 18u -6 u C%sT 1946 -6 V C%sT 1964 Ap 26 2 -5 - EST 1969 @@ -2751,27 +2693,23 @@ Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 -5 - EST 2006 Ap 2 2 -6 u C%sT 2007 N 4 2 -5 u E%sT -R Pe 1946 o - Ap lastSun 2 1 D -R Pe 1946 o - S lastSun 2 0 S -R Pe 1953 1954 - Ap lastSun 2 1 D -R Pe 1953 1959 - S lastSun 2 0 S R Pe 1955 o - May 1 0 1 D -R Pe 1956 1963 - Ap lastSun 2 1 D -R Pe 1960 o - O lastSun 2 0 S -R Pe 1961 o - S lastSun 2 0 S -R Pe 1962 1963 - O lastSun 2 0 S -Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 +R Pe 1955 1960 - S lastSu 2 0 S +R Pe 1956 1963 - Ap lastSu 2 1 D +R Pe 1961 1963 - O lastSu 2 0 S +Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 18u -6 u C%sT 1946 -6 Pe C%sT 1964 Ap 26 2 --5 - EST 1969 +-5 - EST 1967 O 29 2 +-6 u C%sT 1969 Ap 27 2 -5 u E%sT 1971 -5 - EST 2006 Ap 2 2 -6 u C%sT R Pi 1955 o - May 1 0 1 D -R Pi 1955 1960 - S lastSun 2 0 S -R Pi 1956 1964 - Ap lastSun 2 1 D -R Pi 1961 1964 - O lastSun 2 0 S -Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 +R Pi 1955 1960 - S lastSu 2 0 S +R Pi 1956 1964 - Ap lastSu 2 1 D +R Pi 1961 1964 - O lastSu 2 0 S +Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 18u -6 u C%sT 1955 -6 Pi C%sT 1965 Ap 25 2 -5 - EST 1966 O 30 2 @@ -2779,23 +2717,23 @@ Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 -5 - EST 2006 Ap 2 2 -6 u C%sT 2007 N 4 2 -5 u E%sT -R St 1947 1961 - Ap lastSun 2 1 D -R St 1947 1954 - S lastSun 2 0 S -R St 1955 1956 - O lastSun 2 0 S -R St 1957 1958 - S lastSun 2 0 S -R St 1959 1961 - O lastSun 2 0 S -Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 +R St 1947 1961 - Ap lastSu 2 1 D +R St 1947 1954 - S lastSu 2 0 S +R St 1955 1956 - O lastSu 2 0 S +R St 1957 1958 - S lastSu 2 0 S +R St 1959 1961 - O lastSu 2 0 S +Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 18u -6 u C%sT 1947 -6 St C%sT 1962 Ap 29 2 -5 - EST 1963 O 27 2 -6 u C%sT 1991 O 27 2 -5 - EST 2006 Ap 2 2 -6 u C%sT -R Pu 1946 1960 - Ap lastSun 2 1 D -R Pu 1946 1954 - S lastSun 2 0 S -R Pu 1955 1956 - O lastSun 2 0 S -R Pu 1957 1960 - S lastSun 2 0 S -Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 +R Pu 1946 1960 - Ap lastSu 2 1 D +R Pu 1946 1954 - S lastSu 2 0 S +R Pu 1955 1956 - O lastSu 2 0 S +R Pu 1957 1960 - S lastSu 2 0 S +Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 18u -6 u C%sT 1946 -6 Pu C%sT 1961 Ap 30 2 -5 - EST 1969 @@ -2803,7 +2741,7 @@ Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 -5 - EST 2006 Ap 2 2 -6 u C%sT 2007 Mar 11 2 -5 u E%sT -Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 +Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 18u -6 u C%sT 1954 Ap 25 2 -5 - EST 1969 -5 u E%sT 1973 @@ -2811,12 +2749,14 @@ Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 -5 u E%sT R v 1921 o - May 1 2 1 D R v 1921 o - S 1 2 0 S -R v 1941 1961 - Ap lastSun 2 1 D -R v 1941 o - S lastSun 2 0 S +R v 1941 o - Ap lastSu 2 1 D +R v 1941 o - S lastSu 2 0 S +R v 1946 o - Ap lastSu 0:1 1 D R v 1946 o - Jun 2 2 0 S -R v 1950 1955 - S lastSun 2 0 S -R v 1956 1960 - O lastSun 2 0 S -Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 +R v 1950 1961 - Ap lastSu 2 1 D +R v 1950 1955 - S lastSu 2 0 S +R v 1956 1961 - O lastSu 2 0 S +Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 18u -6 u C%sT 1921 -6 v C%sT 1942 -6 u C%sT 1946 @@ -2825,25 +2765,27 @@ Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 -5 u E%sT 1974 Ja 6 2 -6 1 CDT 1974 O 27 2 -5 u E%sT -Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 +Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 18u -6 u C%sT 1946 -6 - CST 1968 -6 u C%sT 2000 O 29 2 -5 u E%sT -R Dt 1948 o - Ap lastSun 2 1 D -R Dt 1948 o - S lastSun 2 0 S +R Dt 1948 o - Ap lastSu 2 1 D +R Dt 1948 o - S lastSu 2 0 S Z America/Detroit -5:32:11 - LMT 1905 -6 - CST 1915 May 15 2 -5 - EST 1942 -5 u E%sT 1946 --5 Dt E%sT 1973 +-5 Dt E%sT 1967 Jun 14 0:1 +-5 u E%sT 1969 +-5 - EST 1973 -5 u E%sT 1975 -5 - EST 1975 Ap 27 2 -5 u E%sT -R Me 1946 o - Ap lastSun 2 1 D -R Me 1946 o - S lastSun 2 0 S -R Me 1966 o - Ap lastSun 2 1 D -R Me 1966 o - O lastSun 2 0 S +R Me 1946 o - Ap lastSu 2 1 D +R Me 1946 o - S lastSu 2 0 S +R Me 1966 o - Ap lastSu 2 1 D +R Me 1966 o - O lastSu 2 0 S Z America/Menominee -5:50:27 - LMT 1885 S 18 12 -6 u C%sT 1946 -6 Me C%sT 1969 Ap 27 2 @@ -2854,30 +2796,30 @@ R C 1918 o - O 27 2 0 S R C 1942 o - F 9 2 1 W R C 1945 o - Au 14 23u 1 P R C 1945 o - S 30 2 0 S -R C 1974 1986 - Ap lastSun 2 1 D -R C 1974 2006 - O lastSun 2 0 S -R C 1987 2006 - Ap Sun>=1 2 1 D -R C 2007 ma - Mar Sun>=8 2 1 D -R C 2007 ma - N Sun>=1 2 0 S +R C 1974 1986 - Ap lastSu 2 1 D +R C 1974 2006 - O lastSu 2 0 S +R C 1987 2006 - Ap Su>=1 2 1 D +R C 2007 ma - Mar Su>=8 2 1 D +R C 2007 ma - N Su>=1 2 0 S R j 1917 o - Ap 8 2 1 D R j 1917 o - S 17 2 0 S R j 1919 o - May 5 23 1 D R j 1919 o - Au 12 23 0 S -R j 1920 1935 - May Sun>=1 23 1 D -R j 1920 1935 - O lastSun 23 0 S +R j 1920 1935 - May Su>=1 23 1 D +R j 1920 1935 - O lastSu 23 0 S R j 1936 1941 - May M>=9 0 1 D R j 1936 1941 - O M>=2 0 0 S -R j 1946 1950 - May Sun>=8 2 1 D -R j 1946 1950 - O Sun>=2 2 0 S -R j 1951 1986 - Ap lastSun 2 1 D -R j 1951 1959 - S lastSun 2 0 S -R j 1960 1986 - O lastSun 2 0 S -R j 1987 o - Ap Sun>=1 0:1 1 D -R j 1987 2006 - O lastSun 0:1 0 S -R j 1988 o - Ap Sun>=1 0:1 2 DD -R j 1989 2006 - Ap Sun>=1 0:1 1 D -R j 2007 2011 - Mar Sun>=8 0:1 1 D -R j 2007 2010 - N Sun>=1 0:1 0 S +R j 1946 1950 - May Su>=8 2 1 D +R j 1946 1950 - O Su>=2 2 0 S +R j 1951 1986 - Ap lastSu 2 1 D +R j 1951 1959 - S lastSu 2 0 S +R j 1960 1986 - O lastSu 2 0 S +R j 1987 o - Ap Su>=1 0:1 1 D +R j 1987 2006 - O lastSu 0:1 0 S +R j 1988 o - Ap Su>=1 0:1 2 DD +R j 1989 2006 - Ap Su>=1 0:1 1 D +R j 2007 2011 - Mar Su>=8 0:1 1 D +R j 2007 2010 - N Su>=1 0:1 0 S Z America/St_Johns -3:30:52 - LMT 1884 -3:30:52 j N%sT 1918 -3:30:52 C N%sT 1919 @@ -2903,7 +2845,7 @@ R H 1920 o - Au 29 0 0 S R H 1921 o - May 6 0 1 D R H 1921 1922 - S 5 0 0 S R H 1922 o - Ap 30 0 1 D -R H 1923 1925 - May Sun>=1 0 1 D +R H 1923 1925 - May Su>=1 0 1 D R H 1923 o - S 4 0 0 S R H 1924 o - S 15 0 0 S R H 1925 o - S 28 0 0 S @@ -2911,7 +2853,7 @@ R H 1926 o - May 16 0 1 D R H 1926 o - S 13 0 0 S R H 1927 o - May 1 0 1 D R H 1927 o - S 26 0 0 S -R H 1928 1931 - May Sun>=8 0 1 D +R H 1928 1931 - May Su>=8 0 1 D R H 1928 o - S 9 0 0 S R H 1929 o - S 3 0 0 S R H 1930 o - S 15 0 0 S @@ -2925,18 +2867,18 @@ R H 1935 o - Jun 2 0 1 D R H 1935 o - S 30 0 0 S R H 1936 o - Jun 1 0 1 D R H 1936 o - S 14 0 0 S -R H 1937 1938 - May Sun>=1 0 1 D +R H 1937 1938 - May Su>=1 0 1 D R H 1937 1941 - S M>=24 0 0 S R H 1939 o - May 28 0 1 D -R H 1940 1941 - May Sun>=1 0 1 D -R H 1946 1949 - Ap lastSun 2 1 D -R H 1946 1949 - S lastSun 2 0 S -R H 1951 1954 - Ap lastSun 2 1 D -R H 1951 1954 - S lastSun 2 0 S -R H 1956 1959 - Ap lastSun 2 1 D -R H 1956 1959 - S lastSun 2 0 S -R H 1962 1973 - Ap lastSun 2 1 D -R H 1962 1973 - O lastSun 2 0 S +R H 1940 1941 - May Su>=1 0 1 D +R H 1946 1949 - Ap lastSu 2 1 D +R H 1946 1949 - S lastSu 2 0 S +R H 1951 1954 - Ap lastSu 2 1 D +R H 1951 1954 - S lastSu 2 0 S +R H 1956 1959 - Ap lastSu 2 1 D +R H 1956 1959 - S lastSu 2 0 S +R H 1962 1973 - Ap lastSu 2 1 D +R H 1962 1973 - O lastSu 2 0 S Z America/Halifax -4:14:24 - LMT 1902 Jun 15 -4 H A%sT 1918 -4 C A%sT 1919 @@ -2950,19 +2892,19 @@ Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 -4 - AST 1972 -4 H A%sT 1974 -4 C A%sT -R o 1933 1935 - Jun Sun>=8 1 1 D -R o 1933 1935 - S Sun>=8 1 0 S -R o 1936 1938 - Jun Sun>=1 1 1 D -R o 1936 1938 - S Sun>=1 1 0 S +R o 1933 1935 - Jun Su>=8 1 1 D +R o 1933 1935 - S Su>=8 1 0 S +R o 1936 1938 - Jun Su>=1 1 1 D +R o 1936 1938 - S Su>=1 1 0 S R o 1939 o - May 27 1 1 D -R o 1939 1941 - S Sat>=21 1 0 S +R o 1939 1941 - S Sa>=21 1 0 S R o 1940 o - May 19 1 1 D R o 1941 o - May 4 1 1 D -R o 1946 1972 - Ap lastSun 2 1 D -R o 1946 1956 - S lastSun 2 0 S -R o 1957 1972 - O lastSun 2 0 S -R o 1993 2006 - Ap Sun>=1 0:1 1 D -R o 1993 2006 - O lastSun 0:1 0 S +R o 1946 1972 - Ap lastSu 2 1 D +R o 1946 1956 - S lastSu 2 0 S +R o 1957 1972 - O lastSu 2 0 S +R o 1993 2006 - Ap Su>=1 0:1 1 D +R o 1993 2006 - O lastSu 0:1 0 S Z America/Moncton -4:19:8 - LMT 1883 D 9 -5 - EST 1902 Jun 15 -4 C A%sT 1933 @@ -2972,59 +2914,34 @@ Z America/Moncton -4:19:8 - LMT 1883 D 9 -4 C A%sT 1993 -4 o A%sT 2007 -4 C A%sT -Z America/Blanc-Sablon -3:48:28 - LMT 1884 --4 C A%sT 1970 --4 - AST R t 1919 o - Mar 30 23:30 1 D R t 1919 o - O 26 0 0 S R t 1920 o - May 2 2 1 D R t 1920 o - S 26 0 0 S R t 1921 o - May 15 2 1 D R t 1921 o - S 15 2 0 S -R t 1922 1923 - May Sun>=8 2 1 D -R t 1922 1926 - S Sun>=15 2 0 S -R t 1924 1927 - May Sun>=1 2 1 D -R t 1927 1932 - S lastSun 2 0 S -R t 1928 1931 - Ap lastSun 2 1 D -R t 1932 o - May 1 2 1 D -R t 1933 1940 - Ap lastSun 2 1 D -R t 1933 o - O 1 2 0 S -R t 1934 1939 - S lastSun 2 0 S -R t 1945 1946 - S lastSun 2 0 S -R t 1946 o - Ap lastSun 2 1 D -R t 1947 1949 - Ap lastSun 0 1 D -R t 1947 1948 - S lastSun 0 0 S -R t 1949 o - N lastSun 0 0 S -R t 1950 1973 - Ap lastSun 2 1 D -R t 1950 o - N lastSun 2 0 S -R t 1951 1956 - S lastSun 2 0 S -R t 1957 1973 - O lastSun 2 0 S +R t 1922 1923 - May Su>=8 2 1 D +R t 1922 1926 - S Su>=15 2 0 S +R t 1924 1927 - May Su>=1 2 1 D +R t 1927 1937 - S Su>=25 2 0 S +R t 1928 1937 - Ap Su>=25 2 1 D +R t 1938 1940 - Ap lastSu 2 1 D +R t 1938 1939 - S lastSu 2 0 S +R t 1945 1946 - S lastSu 2 0 S +R t 1946 o - Ap lastSu 2 1 D +R t 1947 1949 - Ap lastSu 0 1 D +R t 1947 1948 - S lastSu 0 0 S +R t 1949 o - N lastSu 0 0 S +R t 1950 1973 - Ap lastSu 2 1 D +R t 1950 o - N lastSu 2 0 S +R t 1951 1956 - S lastSu 2 0 S +R t 1957 1973 - O lastSu 2 0 S Z America/Toronto -5:17:32 - LMT 1895 -5 C E%sT 1919 -5 t E%sT 1942 F 9 2s -5 C E%sT 1946 -5 t E%sT 1974 -5 C E%sT -Z America/Thunder_Bay -5:57 - LMT 1895 --6 - CST 1910 --5 - EST 1942 --5 C E%sT 1970 --5 t E%sT 1973 --5 - EST 1974 --5 C E%sT -Z America/Nipigon -5:53:4 - LMT 1895 --5 C E%sT 1940 S 29 --5 1 EDT 1942 F 9 2s --5 C E%sT -Z America/Rainy_River -6:18:16 - LMT 1895 --6 C C%sT 1940 S 29 --6 1 CDT 1942 F 9 2s --6 C C%sT -Z America/Atikokan -6:6:28 - LMT 1895 --6 C C%sT 1940 S 29 --6 1 CDT 1942 F 9 2s --6 C C%sT 1945 S 30 2 --5 - EST R W 1916 o - Ap 23 0 1 D R W 1916 o - S 17 0 0 S R W 1918 o - Ap 14 2 1 D @@ -3033,72 +2950,68 @@ R W 1937 o - May 16 2 1 D R W 1937 o - S 26 2 0 S R W 1942 o - F 9 2 1 W R W 1945 o - Au 14 23u 1 P -R W 1945 o - S lastSun 2 0 S +R W 1945 o - S lastSu 2 0 S R W 1946 o - May 12 2 1 D R W 1946 o - O 13 2 0 S -R W 1947 1949 - Ap lastSun 2 1 D -R W 1947 1949 - S lastSun 2 0 S +R W 1947 1949 - Ap lastSu 2 1 D +R W 1947 1949 - S lastSu 2 0 S R W 1950 o - May 1 2 1 D R W 1950 o - S 30 2 0 S -R W 1951 1960 - Ap lastSun 2 1 D -R W 1951 1958 - S lastSun 2 0 S -R W 1959 o - O lastSun 2 0 S -R W 1960 o - S lastSun 2 0 S -R W 1963 o - Ap lastSun 2 1 D +R W 1951 1960 - Ap lastSu 2 1 D +R W 1951 1958 - S lastSu 2 0 S +R W 1959 o - O lastSu 2 0 S +R W 1960 o - S lastSu 2 0 S +R W 1963 o - Ap lastSu 2 1 D R W 1963 o - S 22 2 0 S -R W 1966 1986 - Ap lastSun 2s 1 D -R W 1966 2005 - O lastSun 2s 0 S -R W 1987 2005 - Ap Sun>=1 2s 1 D +R W 1966 1986 - Ap lastSu 2s 1 D +R W 1966 2005 - O lastSu 2s 0 S +R W 1987 2005 - Ap Su>=1 2s 1 D Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 -6 W C%sT 2006 -6 C C%sT R r 1918 o - Ap 14 2 1 D R r 1918 o - O 27 2 0 S -R r 1930 1934 - May Sun>=1 0 1 D -R r 1930 1934 - O Sun>=1 0 0 S -R r 1937 1941 - Ap Sun>=8 0 1 D -R r 1937 o - O Sun>=8 0 0 S -R r 1938 o - O Sun>=1 0 0 S -R r 1939 1941 - O Sun>=8 0 0 S +R r 1930 1934 - May Su>=1 0 1 D +R r 1930 1934 - O Su>=1 0 0 S +R r 1937 1941 - Ap Su>=8 0 1 D +R r 1937 o - O Su>=8 0 0 S +R r 1938 o - O Su>=1 0 0 S +R r 1939 1941 - O Su>=8 0 0 S R r 1942 o - F 9 2 1 W R r 1945 o - Au 14 23u 1 P -R r 1945 o - S lastSun 2 0 S -R r 1946 o - Ap Sun>=8 2 1 D -R r 1946 o - O Sun>=8 2 0 S -R r 1947 1957 - Ap lastSun 2 1 D -R r 1947 1957 - S lastSun 2 0 S -R r 1959 o - Ap lastSun 2 1 D -R r 1959 o - O lastSun 2 0 S -R Sw 1957 o - Ap lastSun 2 1 D -R Sw 1957 o - O lastSun 2 0 S -R Sw 1959 1961 - Ap lastSun 2 1 D -R Sw 1959 o - O lastSun 2 0 S -R Sw 1960 1961 - S lastSun 2 0 S +R r 1945 o - S lastSu 2 0 S +R r 1946 o - Ap Su>=8 2 1 D +R r 1946 o - O Su>=8 2 0 S +R r 1947 1957 - Ap lastSu 2 1 D +R r 1947 1957 - S lastSu 2 0 S +R r 1959 o - Ap lastSu 2 1 D +R r 1959 o - O lastSu 2 0 S +R Sw 1957 o - Ap lastSu 2 1 D +R Sw 1957 o - O lastSu 2 0 S +R Sw 1959 1961 - Ap lastSu 2 1 D +R Sw 1959 o - O lastSu 2 0 S +R Sw 1960 1961 - S lastSu 2 0 S Z America/Regina -6:58:36 - LMT 1905 S --7 r M%sT 1960 Ap lastSun 2 +-7 r M%sT 1960 Ap lastSu 2 -6 - CST Z America/Swift_Current -7:11:20 - LMT 1905 S --7 C M%sT 1946 Ap lastSun 2 +-7 C M%sT 1946 Ap lastSu 2 -7 r M%sT 1950 --7 Sw M%sT 1972 Ap lastSun 2 +-7 Sw M%sT 1972 Ap lastSu 2 -6 - CST -R Ed 1918 1919 - Ap Sun>=8 2 1 D +R Ed 1918 1919 - Ap Su>=8 2 1 D R Ed 1918 o - O 27 2 0 S R Ed 1919 o - May 27 2 0 S -R Ed 1920 1923 - Ap lastSun 2 1 D -R Ed 1920 o - O lastSun 2 0 S -R Ed 1921 1923 - S lastSun 2 0 S +R Ed 1920 1923 - Ap lastSu 2 1 D +R Ed 1920 o - O lastSu 2 0 S +R Ed 1921 1923 - S lastSu 2 0 S R Ed 1942 o - F 9 2 1 W R Ed 1945 o - Au 14 23u 1 P -R Ed 1945 o - S lastSun 2 0 S -R Ed 1947 o - Ap lastSun 2 1 D -R Ed 1947 o - S lastSun 2 0 S -R Ed 1967 o - Ap lastSun 2 1 D -R Ed 1967 o - O lastSun 2 0 S -R Ed 1969 o - Ap lastSun 2 1 D -R Ed 1969 o - O lastSun 2 0 S -R Ed 1972 1986 - Ap lastSun 2 1 D -R Ed 1972 2006 - O lastSun 2 0 S +R Ed 1945 o - S lastSu 2 0 S +R Ed 1947 o - Ap lastSu 2 1 D +R Ed 1947 o - S lastSu 2 0 S +R Ed 1972 1986 - Ap lastSu 2 1 D +R Ed 1972 2006 - O lastSu 2 0 S Z America/Edmonton -7:33:52 - LMT 1906 S -7 Ed M%sT 1987 -7 C M%sT @@ -3107,10 +3020,10 @@ R Va 1918 o - O 27 2 0 S R Va 1942 o - F 9 2 1 W R Va 1945 o - Au 14 23u 1 P R Va 1945 o - S 30 2 0 S -R Va 1946 1986 - Ap lastSun 2 1 D -R Va 1946 o - O 13 2 0 S -R Va 1947 1961 - S lastSun 2 0 S -R Va 1962 2006 - O lastSun 2 0 S +R Va 1946 1986 - Ap lastSu 2 1 D +R Va 1946 o - S 29 2 0 S +R Va 1947 1961 - S lastSu 2 0 S +R Va 1962 2006 - O lastSu 2 0 S Z America/Vancouver -8:12:28 - LMT 1884 -8 Va P%sT 1987 -8 C P%sT @@ -3124,10 +3037,6 @@ Z America/Fort_Nelson -8:10:47 - LMT 1884 -8 Va P%sT 1987 -8 C P%sT 2015 Mar 8 2 -7 - MST -Z America/Creston -7:46:4 - LMT 1884 --7 - MST 1916 O --8 - PST 1918 Jun 2 --7 - MST R Y 1918 o - Ap 14 2 1 D R Y 1918 o - O 27 2 0 S R Y 1919 o - May 25 2 1 D @@ -3135,13 +3044,13 @@ R Y 1919 o - N 1 0 0 S R Y 1942 o - F 9 2 1 W R Y 1945 o - Au 14 23u 1 P R Y 1945 o - S 30 2 0 S -R Y 1965 o - Ap lastSun 0 2 DD -R Y 1965 o - O lastSun 2 0 S -R Y 1980 1986 - Ap lastSun 2 1 D -R Y 1980 2006 - O lastSun 2 0 S -R Y 1987 2006 - Ap Sun>=1 2 1 D +R Y 1965 o - Ap lastSu 0 2 DD +R Y 1965 o - O lastSu 2 0 S +R Y 1980 1986 - Ap lastSu 2 1 D +R Y 1980 2006 - O lastSu 2 0 S +R Y 1987 2006 - Ap Su>=1 2 1 D Z America/Pangnirtung 0 - -00 1921 --4 Y A%sT 1995 Ap Sun>=1 2 +-4 Y A%sT 1995 Ap Su>=1 2 -5 C E%sT 1999 O 31 2 -6 C C%sT 2000 O 29 2 -5 C E%sT @@ -3169,17 +3078,21 @@ Z America/Yellowknife 0 - -00 1935 -7 Y M%sT 1980 -7 C M%sT Z America/Inuvik 0 - -00 1953 --8 Y P%sT 1979 Ap lastSun 2 +-8 Y P%sT 1979 Ap lastSu 2 -7 Y M%sT 1980 -7 C M%sT Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 -9 Y Y%sT 1967 May 28 -8 Y P%sT 1980 --8 C P%sT +-8 C P%sT 2020 N +-7 - MST Z America/Dawson -9:17:40 - LMT 1900 Au 20 -9 Y Y%sT 1973 O 28 -8 Y P%sT 1980 --8 C P%sT +-8 C P%sT 2020 N +-7 - MST +R m 1931 o - May 1 23 1 D +R m 1931 o - O 1 0 0 S R m 1939 o - F 5 0 1 D R m 1939 o - Jun 25 0 0 S R m 1940 o - D 9 0 1 D @@ -3188,93 +3101,83 @@ R m 1943 o - D 16 0 1 W R m 1944 o - May 1 0 0 S R m 1950 o - F 12 0 1 D R m 1950 o - Jul 30 0 0 S -R m 1996 2000 - Ap Sun>=1 2 1 D -R m 1996 2000 - O lastSun 2 0 S -R m 2001 o - May Sun>=1 2 1 D -R m 2001 o - S lastSun 2 0 S -R m 2002 ma - Ap Sun>=1 2 1 D -R m 2002 ma - O lastSun 2 0 S -Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 +R m 1996 2000 - Ap Su>=1 2 1 D +R m 1996 2000 - O lastSu 2 0 S +R m 2001 o - May Su>=1 2 1 D +R m 2001 o - S lastSu 2 0 S +R m 2002 2022 - Ap Su>=1 2 1 D +R m 2002 2022 - O lastSu 2 0 S +Z America/Cancun -5:47:4 - LMT 1922 Ja 1 6u -6 - CST 1981 D 23 -5 m E%sT 1998 Au 2 2 -6 m C%sT 2015 F 1 2 -5 - EST -Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 +Z America/Merida -5:58:28 - LMT 1922 Ja 1 6u -6 - CST 1981 D 23 -5 - EST 1982 D 2 -6 m C%sT -Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 +Z America/Matamoros -6:30 - LMT 1922 Ja 1 6u -6 - CST 1988 -6 u C%sT 1989 -6 m C%sT 2010 -6 u C%sT -Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 +Z America/Monterrey -6:41:16 - LMT 1922 Ja 1 6u -6 - CST 1988 -6 u C%sT 1989 -6 m C%sT -Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 +Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 m C%sT 2001 S 30 2 -6 - CST 2002 F 20 -6 m C%sT -Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 +Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 - CST 1996 -6 m C%sT 1998 --6 - CST 1998 Ap Sun>=1 3 +-6 - CST 1998 Ap Su>=1 3 -7 m M%sT 2010 --7 u M%sT -Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 +-7 u M%sT 2022 O 30 2 +-6 - CST +Z America/Chihuahua -7:4:20 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 - CST 1996 -6 m C%sT 1998 --6 - CST 1998 Ap Sun>=1 3 --7 m M%sT -Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT 2022 O 30 2 +-6 - CST +Z America/Hermosillo -7:23:52 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 - CST 1942 Ap 24 -7 - MST 1949 Ja 14 -8 - PST 1970 -7 m M%sT 1999 -7 - MST -Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 +Z America/Mazatlan -7:5:40 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 - CST 1942 Ap 24 -7 - MST 1949 Ja 14 -8 - PST 1970 -7 m M%sT -Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 +Z America/Bahia_Banderas -7:1 - LMT 1922 Ja 1 7u -7 - MST 1927 Jun 10 23 -6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap +-7 m M%sT 1932 Ap -6 - CST 1942 Ap 24 -7 - MST 1949 Ja 14 -8 - PST 1970 -7 m M%sT 2010 Ap 4 2 -6 m C%sT -Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 +Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 7u -7 - MST 1924 -8 - PST 1927 Jun 10 23 -7 - MST 1930 N 15 @@ -3293,34 +3196,58 @@ Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 -8 u P%sT 2002 F 20 -8 m P%sT 2010 -8 u P%sT -R BS 1964 1975 - O lastSun 2 0 S -R BS 1964 1975 - Ap lastSun 2 1 D -Z America/Nassau -5:9:30 - LMT 1912 Mar 2 --5 BS E%sT 1976 --5 u E%sT +R BB 1942 o - Ap 19 5u 1 D +R BB 1942 o - Au 31 6u 0 S +R BB 1943 o - May 2 5u 1 D +R BB 1943 o - S 5 6u 0 S +R BB 1944 o - Ap 10 5u 0:30 - +R BB 1944 o - S 10 6u 0 S R BB 1977 o - Jun 12 2 1 D -R BB 1977 1978 - O Sun>=1 2 0 S -R BB 1978 1980 - Ap Sun>=15 2 1 D +R BB 1977 1978 - O Su>=1 2 0 S +R BB 1978 1980 - Ap Su>=15 2 1 D R BB 1979 o - S 30 2 0 S R BB 1980 o - S 25 2 0 S -Z America/Barbados -3:58:29 - LMT 1924 --3:58:29 - BMT 1932 +Z America/Barbados -3:58:29 - LMT 1911 Au 28 +-4 BB A%sT 1944 +-4 BB AST/-0330 1945 -4 BB A%sT -R BZ 1918 1942 - O Sun>=2 0 0:30 -0530 -R BZ 1919 1943 - F Sun>=9 0 0 CST +R BZ 1918 1941 - O Sa>=1 24 0:30 -0530 +R BZ 1919 1942 - F Sa>=8 24 0 CST +R BZ 1942 o - Jun 27 24 1 CWT +R BZ 1945 o - Au 14 23u 1 CPT +R BZ 1945 o - D 15 24 0 CST +R BZ 1947 1967 - O Sa>=1 24 0:30 -0530 +R BZ 1948 1968 - F Sa>=8 24 0 CST R BZ 1973 o - D 5 0 1 CDT R BZ 1974 o - F 9 0 0 CST R BZ 1982 o - D 18 0 1 CDT R BZ 1983 o - F 12 0 0 CST Z America/Belize -5:52:48 - LMT 1912 Ap -6 BZ %s -Z Atlantic/Bermuda -4:19:18 - LMT 1930 Ja 1 2 --4 - AST 1974 Ap 28 2 +R Be 1917 o - Ap 5 24 1 - +R Be 1917 o - S 30 24 0 - +R Be 1918 o - Ap 13 24 1 - +R Be 1918 o - S 15 24 0 S +R Be 1942 o - Ja 11 2 1 D +R Be 1942 o - O 18 2 0 S +R Be 1943 o - Mar 21 2 1 D +R Be 1943 o - O 31 2 0 S +R Be 1944 1945 - Mar Su>=8 2 1 D +R Be 1944 1945 - N Su>=1 2 0 S +R Be 1947 o - May Su>=15 2 1 D +R Be 1947 o - S Su>=8 2 0 S +R Be 1948 1952 - May Su>=22 2 1 D +R Be 1948 1952 - S Su>=1 2 0 S +R Be 1956 o - May Su>=22 2 1 D +R Be 1956 o - O lastSu 2 0 S +Z Atlantic/Bermuda -4:19:18 - LMT 1890 +-4:19:18 Be BMT/BST 1930 Ja 1 2 +-4 Be A%sT 1974 Ap 28 2 -4 C A%sT 1976 -4 u A%sT -R CR 1979 1980 - F lastSun 0 1 D -R CR 1979 1980 - Jun Sun>=1 0 0 S -R CR 1991 1992 - Ja Sat>=15 0 1 D +R CR 1979 1980 - F lastSu 0 1 D +R CR 1979 1980 - Jun Su>=1 0 0 S +R CR 1991 1992 - Ja Sa>=15 0 1 D R CR 1991 o - Jul 1 0 0 S R CR 1992 o - Mar 15 0 0 S Z America/Costa_Rica -5:36:13 - LMT 1890 @@ -3328,49 +3255,49 @@ Z America/Costa_Rica -5:36:13 - LMT 1890 -6 CR C%sT R Q 1928 o - Jun 10 0 1 D R Q 1928 o - O 10 0 0 S -R Q 1940 1942 - Jun Sun>=1 0 1 D -R Q 1940 1942 - S Sun>=1 0 0 S -R Q 1945 1946 - Jun Sun>=1 0 1 D -R Q 1945 1946 - S Sun>=1 0 0 S +R Q 1940 1942 - Jun Su>=1 0 1 D +R Q 1940 1942 - S Su>=1 0 0 S +R Q 1945 1946 - Jun Su>=1 0 1 D +R Q 1945 1946 - S Su>=1 0 0 S R Q 1965 o - Jun 1 0 1 D R Q 1965 o - S 30 0 0 S R Q 1966 o - May 29 0 1 D R Q 1966 o - O 2 0 0 S R Q 1967 o - Ap 8 0 1 D -R Q 1967 1968 - S Sun>=8 0 0 S +R Q 1967 1968 - S Su>=8 0 0 S R Q 1968 o - Ap 14 0 1 D -R Q 1969 1977 - Ap lastSun 0 1 D -R Q 1969 1971 - O lastSun 0 0 S +R Q 1969 1977 - Ap lastSu 0 1 D +R Q 1969 1971 - O lastSu 0 0 S R Q 1972 1974 - O 8 0 0 S -R Q 1975 1977 - O lastSun 0 0 S +R Q 1975 1977 - O lastSu 0 0 S R Q 1978 o - May 7 0 1 D -R Q 1978 1990 - O Sun>=8 0 0 S -R Q 1979 1980 - Mar Sun>=15 0 1 D -R Q 1981 1985 - May Sun>=5 0 1 D -R Q 1986 1989 - Mar Sun>=14 0 1 D -R Q 1990 1997 - Ap Sun>=1 0 1 D -R Q 1991 1995 - O Sun>=8 0s 0 S +R Q 1978 1990 - O Su>=8 0 0 S +R Q 1979 1980 - Mar Su>=15 0 1 D +R Q 1981 1985 - May Su>=5 0 1 D +R Q 1986 1989 - Mar Su>=14 0 1 D +R Q 1990 1997 - Ap Su>=1 0 1 D +R Q 1991 1995 - O Su>=8 0s 0 S R Q 1996 o - O 6 0s 0 S R Q 1997 o - O 12 0s 0 S -R Q 1998 1999 - Mar lastSun 0s 1 D -R Q 1998 2003 - O lastSun 0s 0 S -R Q 2000 2003 - Ap Sun>=1 0s 1 D -R Q 2004 o - Mar lastSun 0s 1 D -R Q 2006 2010 - O lastSun 0s 0 S -R Q 2007 o - Mar Sun>=8 0s 1 D -R Q 2008 o - Mar Sun>=15 0s 1 D -R Q 2009 2010 - Mar Sun>=8 0s 1 D -R Q 2011 o - Mar Sun>=15 0s 1 D +R Q 1998 1999 - Mar lastSu 0s 1 D +R Q 1998 2003 - O lastSu 0s 0 S +R Q 2000 2003 - Ap Su>=1 0s 1 D +R Q 2004 o - Mar lastSu 0s 1 D +R Q 2006 2010 - O lastSu 0s 0 S +R Q 2007 o - Mar Su>=8 0s 1 D +R Q 2008 o - Mar Su>=15 0s 1 D +R Q 2009 2010 - Mar Su>=8 0s 1 D +R Q 2011 o - Mar Su>=15 0s 1 D R Q 2011 o - N 13 0s 0 S R Q 2012 o - Ap 1 0s 1 D -R Q 2012 ma - N Sun>=1 0s 0 S -R Q 2013 ma - Mar Sun>=8 0s 1 D +R Q 2012 ma - N Su>=1 0s 0 S +R Q 2013 ma - Mar Su>=8 0s 1 D Z America/Havana -5:29:28 - LMT 1890 -5:29:36 - HMT 1925 Jul 19 12 -5 Q C%sT R DO 1966 o - O 30 0 1 EDT R DO 1967 o - F 28 0 0 EST -R DO 1969 1973 - O lastSun 0 0:30 -0430 +R DO 1969 1973 - O lastSu 0 0:30 -0430 R DO 1970 o - F 21 0 0 EST R DO 1971 o - Ja 20 0 0 EST R DO 1972 1974 - Ja 21 0 0 EST @@ -3380,8 +3307,8 @@ Z America/Santo_Domingo -4:39:36 - LMT 1890 -4 - AST 2000 O 29 2 -5 u E%sT 2000 D 3 1 -4 - AST -R SV 1987 1988 - May Sun>=1 0 1 D -R SV 1987 1988 - S lastSun 0 0 S +R SV 1987 1988 - May Su>=1 0 1 D +R SV 1987 1988 - S lastSu 0 0 S Z America/El_Salvador -5:56:48 - LMT 1921 -6 SV C%sT R GT 1973 o - N 25 0 1 D @@ -3395,22 +3322,22 @@ R GT 2006 o - O 1 0 0 S Z America/Guatemala -6:2:4 - LMT 1918 O 5 -6 GT C%sT R HT 1983 o - May 8 0 1 D -R HT 1984 1987 - Ap lastSun 0 1 D -R HT 1983 1987 - O lastSun 0 0 S -R HT 1988 1997 - Ap Sun>=1 1s 1 D -R HT 1988 1997 - O lastSun 1s 0 S -R HT 2005 2006 - Ap Sun>=1 0 1 D -R HT 2005 2006 - O lastSun 0 0 S -R HT 2012 2015 - Mar Sun>=8 2 1 D -R HT 2012 2015 - N Sun>=1 2 0 S -R HT 2017 ma - Mar Sun>=8 2 1 D -R HT 2017 ma - N Sun>=1 2 0 S +R HT 1984 1987 - Ap lastSu 0 1 D +R HT 1983 1987 - O lastSu 0 0 S +R HT 1988 1997 - Ap Su>=1 1s 1 D +R HT 1988 1997 - O lastSu 1s 0 S +R HT 2005 2006 - Ap Su>=1 0 1 D +R HT 2005 2006 - O lastSu 0 0 S +R HT 2012 2015 - Mar Su>=8 2 1 D +R HT 2012 2015 - N Su>=1 2 0 S +R HT 2017 ma - Mar Su>=8 2 1 D +R HT 2017 ma - N Su>=1 2 0 S Z America/Port-au-Prince -4:49:20 - LMT 1890 -4:49 - PPMT 1917 Ja 24 12 -5 HT E%sT -R HN 1987 1988 - May Sun>=1 0 1 D -R HN 1987 1988 - S lastSun 0 0 S -R HN 2006 o - May Sun>=1 0 1 D +R HN 1987 1988 - May Su>=1 0 1 D +R HN 1987 1988 - S lastSu 0 0 S +R HN 2006 o - May Su>=1 0 1 D R HN 2006 o - Au M>=1 0 0 S Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap -6 HN C%sT @@ -3424,12 +3351,12 @@ Z America/Martinique -4:4:20 - LMT 1890 -4 - AST 1980 Ap 6 -4 1 ADT 1980 S 28 -4 - AST -R NI 1979 1980 - Mar Sun>=16 0 1 D +R NI 1979 1980 - Mar Su>=16 0 1 D R NI 1979 1980 - Jun M>=23 0 0 S R NI 2005 o - Ap 10 0 1 D -R NI 2005 o - O Sun>=1 0 0 S +R NI 2005 o - O Su>=1 0 0 S R NI 2006 o - Ap 30 2 1 D -R NI 2006 o - O Sun>=1 1 0 S +R NI 2006 o - O Su>=1 1 0 S Z America/Managua -5:45:8 - LMT 1890 -5:45:12 - MMT 1934 Jun 23 -6 - CST 1973 May @@ -3442,7 +3369,6 @@ Z America/Managua -5:45:8 - LMT 1890 Z America/Panama -5:18:8 - LMT 1890 -5:19:36 - CMT 1908 Ap 22 -5 - EST -Li America/Panama America/Cayman Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 -4 - AST 1942 May 3 -4 u A%sT 1946 @@ -3454,7 +3380,7 @@ Z America/Miquelon -3:44:40 - LMT 1911 May 15 Z America/Grand_Turk -4:44:32 - LMT 1890 -5:7:10 - KMT 1912 F -5 - EST 1979 --5 u E%sT 2015 N Sun>=1 2 +-5 u E%sT 2015 Mar 8 2 -4 - AST 2018 Mar 11 3 -5 u E%sT R A 1930 o - D 1 0 1 - @@ -3474,18 +3400,18 @@ R A 1963 o - D 15 0 1 - R A 1964 1966 - Mar 1 0 0 - R A 1964 1966 - O 15 0 1 - R A 1967 o - Ap 2 0 0 - -R A 1967 1968 - O Sun>=1 0 1 - -R A 1968 1969 - Ap Sun>=1 0 0 - +R A 1967 1968 - O Su>=1 0 1 - +R A 1968 1969 - Ap Su>=1 0 0 - R A 1974 o - Ja 23 0 1 - R A 1974 o - May 1 0 0 - R A 1988 o - D 1 0 1 - -R A 1989 1993 - Mar Sun>=1 0 0 - -R A 1989 1992 - O Sun>=15 0 1 - -R A 1999 o - O Sun>=1 0 1 - +R A 1989 1993 - Mar Su>=1 0 0 - +R A 1989 1992 - O Su>=15 0 1 - +R A 1999 o - O Su>=1 0 1 - R A 2000 o - Mar 3 0 0 - R A 2007 o - D 30 0 1 - -R A 2008 2009 - Mar Sun>=15 0 0 - -R A 2008 o - O Sun>=15 0 1 - +R A 2008 2009 - Mar Su>=15 0 0 - +R A 2008 o - O Su>=15 0 1 - Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 -4:16:48 - CMT 1920 May -4 - -04 1930 D @@ -3588,8 +3514,8 @@ Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 -4 - -04 2004 S 26 -3 A -03/-02 2008 O 18 -3 - -03 -R Sa 2008 2009 - Mar Sun>=8 0 0 - -R Sa 2007 2008 - O Sun>=8 0 1 - +R Sa 2008 2009 - Mar Su>=8 0 0 - +R Sa 2007 2008 - O Su>=8 0 1 - Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 -4:16:48 - CMT 1920 May -4 - -04 1930 D @@ -3626,7 +3552,6 @@ Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 -4 - -04 2004 Jun 20 -3 A -03/-02 2008 O 18 -3 - -03 -Li America/Curacao America/Aruba Z America/La_Paz -4:32:36 - LMT 1890 -4:32:36 - CMT 1931 O 15 -4:32:36 1 BST 1932 Mar 21 @@ -3661,8 +3586,8 @@ R B 1991 o - O 20 0 1 - R B 1992 o - F 9 0 0 - R B 1992 o - O 25 0 1 - R B 1993 o - Ja 31 0 0 - -R B 1993 1995 - O Sun>=11 0 1 - -R B 1994 1995 - F Sun>=15 0 0 - +R B 1993 1995 - O Su>=11 0 1 - +R B 1994 1995 - F Su>=15 0 0 - R B 1996 o - F 11 0 0 - R B 1996 o - O 6 0 1 - R B 1997 o - F 16 0 0 - @@ -3672,30 +3597,22 @@ R B 1998 o - O 11 0 1 - R B 1999 o - F 21 0 0 - R B 1999 o - O 3 0 1 - R B 2000 o - F 27 0 0 - -R B 2000 2001 - O Sun>=8 0 1 - -R B 2001 2006 - F Sun>=15 0 0 - +R B 2000 2001 - O Su>=8 0 1 - +R B 2001 2006 - F Su>=15 0 0 - R B 2002 o - N 3 0 1 - R B 2003 o - O 19 0 1 - R B 2004 o - N 2 0 1 - R B 2005 o - O 16 0 1 - R B 2006 o - N 5 0 1 - R B 2007 o - F 25 0 0 - -R B 2007 o - O Sun>=8 0 1 - -R B 2008 2017 - O Sun>=15 0 1 - -R B 2008 2011 - F Sun>=15 0 0 - -R B 2012 o - F Sun>=22 0 0 - -R B 2013 2014 - F Sun>=15 0 0 - -R B 2015 o - F Sun>=22 0 0 - -R B 2016 2022 - F Sun>=15 0 0 - -R B 2018 ma - N Sun>=1 0 1 - -R B 2023 o - F Sun>=22 0 0 - -R B 2024 2025 - F Sun>=15 0 0 - -R B 2026 o - F Sun>=22 0 0 - -R B 2027 2033 - F Sun>=15 0 0 - -R B 2034 o - F Sun>=22 0 0 - -R B 2035 2036 - F Sun>=15 0 0 - -R B 2037 o - F Sun>=22 0 0 - -R B 2038 ma - F Sun>=15 0 0 - +R B 2007 o - O Su>=8 0 1 - +R B 2008 2017 - O Su>=15 0 1 - +R B 2008 2011 - F Su>=15 0 0 - +R B 2012 o - F Su>=22 0 0 - +R B 2013 2014 - F Su>=15 0 0 - +R B 2015 o - F Su>=22 0 0 - +R B 2016 2019 - F Su>=15 0 0 - +R B 2018 o - N Su>=1 0 1 - Z America/Noronha -2:9:40 - LMT 1914 -2 B -02/-01 1990 S 17 -2 - -02 1999 S 30 @@ -3787,57 +3704,60 @@ R x 1969 o - Mar 30 3u 0 - R x 1969 o - N 23 4u 1 - R x 1970 o - Mar 29 3u 0 - R x 1971 o - Mar 14 3u 0 - -R x 1970 1972 - O Sun>=9 4u 1 - -R x 1972 1986 - Mar Sun>=9 3u 0 - +R x 1970 1972 - O Su>=9 4u 1 - +R x 1972 1986 - Mar Su>=9 3u 0 - R x 1973 o - S 30 4u 1 - -R x 1974 1987 - O Sun>=9 4u 1 - +R x 1974 1987 - O Su>=9 4u 1 - R x 1987 o - Ap 12 3u 0 - -R x 1988 1990 - Mar Sun>=9 3u 0 - -R x 1988 1989 - O Sun>=9 4u 1 - +R x 1988 1990 - Mar Su>=9 3u 0 - +R x 1988 1989 - O Su>=9 4u 1 - R x 1990 o - S 16 4u 1 - -R x 1991 1996 - Mar Sun>=9 3u 0 - -R x 1991 1997 - O Sun>=9 4u 1 - +R x 1991 1996 - Mar Su>=9 3u 0 - +R x 1991 1997 - O Su>=9 4u 1 - R x 1997 o - Mar 30 3u 0 - -R x 1998 o - Mar Sun>=9 3u 0 - +R x 1998 o - Mar Su>=9 3u 0 - R x 1998 o - S 27 4u 1 - R x 1999 o - Ap 4 3u 0 - -R x 1999 2010 - O Sun>=9 4u 1 - -R x 2000 2007 - Mar Sun>=9 3u 0 - +R x 1999 2010 - O Su>=9 4u 1 - +R x 2000 2007 - Mar Su>=9 3u 0 - R x 2008 o - Mar 30 3u 0 - -R x 2009 o - Mar Sun>=9 3u 0 - -R x 2010 o - Ap Sun>=1 3u 0 - -R x 2011 o - May Sun>=2 3u 0 - -R x 2011 o - Au Sun>=16 4u 1 - -R x 2012 2014 - Ap Sun>=23 3u 0 - -R x 2012 2014 - S Sun>=2 4u 1 - -R x 2016 2018 - May Sun>=9 3u 0 - -R x 2016 2018 - Au Sun>=9 4u 1 - -R x 2019 ma - Ap Sun>=2 3u 0 - -R x 2019 ma - S Sun>=2 4u 1 - -Z America/Santiago -4:42:46 - LMT 1890 --4:42:46 - SMT 1910 Ja 10 +R x 2009 o - Mar Su>=9 3u 0 - +R x 2010 o - Ap Su>=1 3u 0 - +R x 2011 o - May Su>=2 3u 0 - +R x 2011 o - Au Su>=16 4u 1 - +R x 2012 2014 - Ap Su>=23 3u 0 - +R x 2012 2014 - S Su>=2 4u 1 - +R x 2016 2018 - May Su>=9 3u 0 - +R x 2016 2018 - Au Su>=9 4u 1 - +R x 2019 ma - Ap Su>=2 3u 0 - +R x 2019 2021 - S Su>=2 4u 1 - +R x 2022 o - S Su>=9 4u 1 - +R x 2023 ma - S Su>=2 4u 1 - +Z America/Santiago -4:42:45 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 -5 - -05 1916 Jul --4:42:46 - SMT 1918 S 10 +-4:42:45 - SMT 1918 S 10 -4 - -04 1919 Jul --4:42:46 - SMT 1927 S +-4:42:45 - SMT 1927 S -5 x -05/-04 1932 S -4 - -04 1942 Jun -5 - -05 1942 Au --4 - -04 1946 Jul 15 --4 1 -03 1946 S --4 - -04 1947 Ap +-4 - -04 1946 Jul 14 24 +-4 1 -03 1946 Au 28 24 +-5 1 -04 1947 Mar 31 24 -5 - -05 1947 May 21 23 -4 x -04/-03 Z America/Punta_Arenas -4:43:40 - LMT 1890 --4:42:46 - SMT 1910 Ja 10 +-4:42:45 - SMT 1910 Ja 10 -5 - -05 1916 Jul --4:42:46 - SMT 1918 S 10 +-4:42:45 - SMT 1918 S 10 -4 - -04 1919 Jul --4:42:46 - SMT 1927 S +-4:42:45 - SMT 1927 S -5 x -05/-04 1932 S -4 - -04 1942 Jun -5 - -05 1942 Au --4 - -04 1947 Ap +-4 - -04 1946 Au 28 24 +-5 1 -04 1947 Mar 31 24 -5 - -05 1947 May 21 23 -4 x -04/-03 2016 D 4 -3 - -03 @@ -3855,11 +3775,6 @@ R CO 1993 o - Ap 4 0 0 - Z America/Bogota -4:56:16 - LMT 1884 Mar 13 -4:56:16 - BMT 1914 N 23 -5 CO -05/-04 -Z America/Curacao -4:35:47 - LMT 1912 F 12 --4:30 - -0430 1965 --4 - AST -Li America/Curacao America/Lower_Princes -Li America/Curacao America/Kralendijk R EC 1992 o - N 28 0 1 - R EC 1993 o - F 5 0 0 - Z America/Guayaquil -5:19:20 - LMT 1890 @@ -3868,18 +3783,18 @@ Z America/Guayaquil -5:19:20 - LMT 1890 Z Pacific/Galapagos -5:58:24 - LMT 1931 -5 - -05 1986 -6 EC -06/-05 -R FK 1937 1938 - S lastSun 0 1 - -R FK 1938 1942 - Mar Sun>=19 0 0 - +R FK 1937 1938 - S lastSu 0 1 - +R FK 1938 1942 - Mar Su>=19 0 0 - R FK 1939 o - O 1 0 1 - -R FK 1940 1942 - S lastSun 0 1 - +R FK 1940 1942 - S lastSu 0 1 - R FK 1943 o - Ja 1 0 0 - -R FK 1983 o - S lastSun 0 1 - -R FK 1984 1985 - Ap lastSun 0 0 - +R FK 1983 o - S lastSu 0 1 - +R FK 1984 1985 - Ap lastSu 0 0 - R FK 1984 o - S 16 0 1 - -R FK 1985 2000 - S Sun>=9 0 1 - -R FK 1986 2000 - Ap Sun>=16 0 0 - -R FK 2001 2010 - Ap Sun>=15 2 0 - -R FK 2001 2010 - S Sun>=1 2 1 - +R FK 1985 2000 - S Su>=9 0 1 - +R FK 1986 2000 - Ap Su>=16 0 0 - +R FK 2001 2010 - Ap Su>=15 2 0 - +R FK 2001 2010 - S Su>=1 2 1 - Z Atlantic/Stanley -3:51:24 - LMT 1890 -3:51:24 - SMT 1912 Mar 12 -4 FK -04/-03 1983 May @@ -3889,9 +3804,10 @@ Z Atlantic/Stanley -3:51:24 - LMT 1890 Z America/Cayenne -3:29:20 - LMT 1911 Jul -4 - -04 1967 O -3 - -03 -Z America/Guyana -3:52:40 - LMT 1915 Mar --3:45 - -0345 1975 Jul 31 --3 - -03 1991 +Z America/Guyana -3:52:39 - LMT 1911 Au +-4 - -04 1915 Mar +-3:45 - -0345 1975 Au +-3 - -03 1992 Mar 29 1 -4 - -04 R y 1975 1988 - O 1 0 1 - R y 1975 1978 - Mar 1 0 0 - @@ -3903,18 +3819,18 @@ R y 1992 o - Mar 1 0 0 - R y 1992 o - O 5 0 1 - R y 1993 o - Mar 31 0 0 - R y 1993 1995 - O 1 0 1 - -R y 1994 1995 - F lastSun 0 0 - +R y 1994 1995 - F lastSu 0 0 - R y 1996 o - Mar 1 0 0 - -R y 1996 2001 - O Sun>=1 0 1 - -R y 1997 o - F lastSun 0 0 - -R y 1998 2001 - Mar Sun>=1 0 0 - -R y 2002 2004 - Ap Sun>=1 0 0 - -R y 2002 2003 - S Sun>=1 0 1 - -R y 2004 2009 - O Sun>=15 0 1 - -R y 2005 2009 - Mar Sun>=8 0 0 - -R y 2010 ma - O Sun>=1 0 1 - -R y 2010 2012 - Ap Sun>=8 0 0 - -R y 2013 ma - Mar Sun>=22 0 0 - +R y 1996 2001 - O Su>=1 0 1 - +R y 1997 o - F lastSu 0 0 - +R y 1998 2001 - Mar Su>=1 0 0 - +R y 2002 2004 - Ap Su>=1 0 0 - +R y 2002 2003 - S Su>=1 0 1 - +R y 2004 2009 - O Su>=15 0 1 - +R y 2005 2009 - Mar Su>=8 0 0 - +R y 2010 ma - O Su>=1 0 1 - +R y 2010 2012 - Ap Su>=8 0 0 - +R y 2013 ma - Mar Su>=22 0 0 - Z America/Asuncion -3:50:40 - LMT 1890 -3:50:40 - AMT 1931 O 10 -4 - -04 1972 O @@ -3922,8 +3838,8 @@ Z America/Asuncion -3:50:40 - LMT 1890 -4 y -04/-03 R PE 1938 o - Ja 1 0 1 - R PE 1938 o - Ap 1 0 0 - -R PE 1938 1939 - S lastSun 0 1 - -R PE 1939 1940 - Mar Sun>=24 0 0 - +R PE 1938 1939 - S lastSu 0 1 - +R PE 1939 1940 - Mar Su>=24 0 0 - R PE 1986 1987 - Ja 1 0 1 - R PE 1986 1987 - Ap 1 0 0 - R PE 1990 o - Ja 1 0 1 - @@ -3940,25 +3856,10 @@ Z America/Paramaribo -3:40:40 - LMT 1911 -3:40:36 - PMT 1945 O -3:30 - -0330 1984 O -3 - -03 -Z America/Port_of_Spain -4:6:4 - LMT 1912 Mar 2 --4 - AST -Li America/Port_of_Spain America/Anguilla -Li America/Port_of_Spain America/Antigua -Li America/Port_of_Spain America/Dominica -Li America/Port_of_Spain America/Grenada -Li America/Port_of_Spain America/Guadeloupe -Li America/Port_of_Spain America/Marigot -Li America/Port_of_Spain America/Montserrat -Li America/Port_of_Spain America/St_Barthelemy -Li America/Port_of_Spain America/St_Kitts -Li America/Port_of_Spain America/St_Lucia -Li America/Port_of_Spain America/St_Thomas -Li America/Port_of_Spain America/St_Vincent -Li America/Port_of_Spain America/Tortola R U 1923 1925 - O 1 0 0:30 - R U 1924 1926 - Ap 1 0 0 - -R U 1933 1938 - O lastSun 0 0:30 - -R U 1934 1941 - Mar lastSat 24 0 - +R U 1933 1938 - O lastSu 0 0:30 - +R U 1934 1941 - Mar lastSa 24 0 - R U 1939 o - O 1 0 0:30 - R U 1940 o - O 27 0 0:30 - R U 1941 o - Au 1 0 0:30 - @@ -3984,7 +3885,7 @@ R U 1975 o - Mar 30 0 0 - R U 1976 o - D 19 0 1 - R U 1977 o - Mar 6 0 0 - R U 1977 o - D 4 0 1 - -R U 1978 1979 - Mar Sun>=1 0 0 - +R U 1978 1979 - Mar Su>=1 0 0 - R U 1978 o - D 17 0 1 - R U 1979 o - Ap 29 0 1 - R U 1980 o - Mar 16 0 0 - @@ -3994,15 +3895,15 @@ R U 1988 o - D 11 0 1 - R U 1989 o - Mar 5 0 0 - R U 1989 o - O 29 0 1 - R U 1990 o - F 25 0 0 - -R U 1990 1991 - O Sun>=21 0 1 - -R U 1991 1992 - Mar Sun>=1 0 0 - +R U 1990 1991 - O Su>=21 0 1 - +R U 1991 1992 - Mar Su>=1 0 0 - R U 1992 o - O 18 0 1 - R U 1993 o - F 28 0 0 - R U 2004 o - S 19 0 1 - R U 2005 o - Mar 27 2 0 - R U 2005 o - O 9 2 1 - -R U 2006 2015 - Mar Sun>=8 2 0 - -R U 2006 2014 - O Sun>=1 2 1 - +R U 2006 2015 - Mar Su>=8 2 0 - +R U 2006 2014 - O Su>=1 2 1 - Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 -3:44:51 - MMT 1920 May -4 - -04 1923 O @@ -4020,16 +3921,9 @@ Z America/Caracas -4:27:44 - LMT 1890 -4 - -04 2007 D 9 3 -4:30 - -0430 2016 May 1 2:30 -4 - -04 -Z Etc/GMT 0 - GMT Z Etc/UTC 0 - UTC -Z Etc/UCT 0 - UCT -Li Etc/GMT GMT -Li Etc/UTC Etc/Universal -Li Etc/UTC Etc/Zulu -Li Etc/GMT Etc/Greenwich -Li Etc/GMT Etc/GMT-0 -Li Etc/GMT Etc/GMT+0 -Li Etc/GMT Etc/GMT0 +Z Etc/GMT 0 - GMT +L Etc/GMT GMT Z Etc/GMT-14 14 - +14 Z Etc/GMT-13 13 - +13 Z Etc/GMT-12 12 - +12 @@ -4057,121 +3951,246 @@ Z Etc/GMT+10 -10 - -10 Z Etc/GMT+11 -11 - -11 Z Etc/GMT+12 -12 - -12 Z Factory 0 - -00 -Li Africa/Nairobi Africa/Asmera -Li Africa/Abidjan Africa/Timbuktu -Li America/Argentina/Catamarca America/Argentina/ComodRivadavia -Li America/Adak America/Atka -Li America/Argentina/Buenos_Aires America/Buenos_Aires -Li America/Argentina/Catamarca America/Catamarca -Li America/Atikokan America/Coral_Harbour -Li America/Argentina/Cordoba America/Cordoba -Li America/Tijuana America/Ensenada -Li America/Indiana/Indianapolis America/Fort_Wayne -Li America/Indiana/Indianapolis America/Indianapolis -Li America/Argentina/Jujuy America/Jujuy -Li America/Indiana/Knox America/Knox_IN -Li America/Kentucky/Louisville America/Louisville -Li America/Argentina/Mendoza America/Mendoza -Li America/Toronto America/Montreal -Li America/Rio_Branco America/Porto_Acre -Li America/Argentina/Cordoba America/Rosario -Li America/Tijuana America/Santa_Isabel -Li America/Denver America/Shiprock -Li America/Port_of_Spain America/Virgin -Li Pacific/Auckland Antarctica/South_Pole -Li Asia/Ashgabat Asia/Ashkhabad -Li Asia/Kolkata Asia/Calcutta -Li Asia/Shanghai Asia/Chongqing -Li Asia/Shanghai Asia/Chungking -Li Asia/Dhaka Asia/Dacca -Li Asia/Shanghai Asia/Harbin -Li Asia/Urumqi Asia/Kashgar -Li Asia/Kathmandu Asia/Katmandu -Li Asia/Macau Asia/Macao -Li Asia/Yangon Asia/Rangoon -Li Asia/Ho_Chi_Minh Asia/Saigon -Li Asia/Jerusalem Asia/Tel_Aviv -Li Asia/Thimphu Asia/Thimbu -Li Asia/Makassar Asia/Ujung_Pandang -Li Asia/Ulaanbaatar Asia/Ulan_Bator -Li Atlantic/Faroe Atlantic/Faeroe -Li Europe/Oslo Atlantic/Jan_Mayen -Li Australia/Sydney Australia/ACT -Li Australia/Sydney Australia/Canberra -Li Australia/Lord_Howe Australia/LHI -Li Australia/Sydney Australia/NSW -Li Australia/Darwin Australia/North -Li Australia/Brisbane Australia/Queensland -Li Australia/Adelaide Australia/South -Li Australia/Hobart Australia/Tasmania -Li Australia/Melbourne Australia/Victoria -Li Australia/Perth Australia/West -Li Australia/Broken_Hill Australia/Yancowinna -Li America/Rio_Branco Brazil/Acre -Li America/Noronha Brazil/DeNoronha -Li America/Sao_Paulo Brazil/East -Li America/Manaus Brazil/West -Li America/Halifax Canada/Atlantic -Li America/Winnipeg Canada/Central -Li America/Toronto Canada/Eastern -Li America/Edmonton Canada/Mountain -Li America/St_Johns Canada/Newfoundland -Li America/Vancouver Canada/Pacific -Li America/Regina Canada/Saskatchewan -Li America/Whitehorse Canada/Yukon -Li America/Santiago Chile/Continental -Li Pacific/Easter Chile/EasterIsland -Li America/Havana Cuba -Li Africa/Cairo Egypt -Li Europe/Dublin Eire -Li Europe/London Europe/Belfast -Li Europe/Chisinau Europe/Tiraspol -Li Europe/London GB -Li Europe/London GB-Eire -Li Etc/GMT GMT+0 -Li Etc/GMT GMT-0 -Li Etc/GMT GMT0 -Li Etc/GMT Greenwich -Li Asia/Hong_Kong Hongkong -Li Atlantic/Reykjavik Iceland -Li Asia/Tehran Iran -Li Asia/Jerusalem Israel -Li America/Jamaica Jamaica -Li Asia/Tokyo Japan -Li Pacific/Kwajalein Kwajalein -Li Africa/Tripoli Libya -Li America/Tijuana Mexico/BajaNorte -Li America/Mazatlan Mexico/BajaSur -Li America/Mexico_City Mexico/General -Li Pacific/Auckland NZ -Li Pacific/Chatham NZ-CHAT -Li America/Denver Navajo -Li Asia/Shanghai PRC -Li Pacific/Honolulu Pacific/Johnston -Li Pacific/Pohnpei Pacific/Ponape -Li Pacific/Pago_Pago Pacific/Samoa -Li Pacific/Chuuk Pacific/Truk -Li Pacific/Chuuk Pacific/Yap -Li Europe/Warsaw Poland -Li Europe/Lisbon Portugal -Li Asia/Taipei ROC -Li Asia/Seoul ROK -Li Asia/Singapore Singapore -Li Europe/Istanbul Turkey -Li Etc/UCT UCT -Li America/Anchorage US/Alaska -Li America/Adak US/Aleutian -Li America/Phoenix US/Arizona -Li America/Chicago US/Central -Li America/Indiana/Indianapolis US/East-Indiana -Li America/New_York US/Eastern -Li Pacific/Honolulu US/Hawaii -Li America/Indiana/Knox US/Indiana-Starke -Li America/Detroit US/Michigan -Li America/Denver US/Mountain -Li America/Los_Angeles US/Pacific -Li Pacific/Pago_Pago US/Samoa -Li Etc/UTC UTC -Li Etc/UTC Universal -Li Europe/Moscow W-SU -Li Etc/UTC Zulu +L Australia/Sydney Australia/ACT +L Australia/Lord_Howe Australia/LHI +L Australia/Sydney Australia/NSW +L Australia/Darwin Australia/North +L Australia/Brisbane Australia/Queensland +L Australia/Adelaide Australia/South +L Australia/Hobart Australia/Tasmania +L Australia/Melbourne Australia/Victoria +L Australia/Perth Australia/West +L Australia/Broken_Hill Australia/Yancowinna +L America/Rio_Branco Brazil/Acre +L America/Noronha Brazil/DeNoronha +L America/Sao_Paulo Brazil/East +L America/Manaus Brazil/West +L America/Halifax Canada/Atlantic +L America/Winnipeg Canada/Central +L America/Toronto Canada/Eastern +L America/Edmonton Canada/Mountain +L America/St_Johns Canada/Newfoundland +L America/Vancouver Canada/Pacific +L America/Regina Canada/Saskatchewan +L America/Whitehorse Canada/Yukon +L America/Santiago Chile/Continental +L Pacific/Easter Chile/EasterIsland +L America/Havana Cuba +L Africa/Cairo Egypt +L Europe/Dublin Eire +L Etc/GMT Etc/GMT+0 +L Etc/GMT Etc/GMT-0 +L Etc/GMT Etc/GMT0 +L Etc/GMT Etc/Greenwich +L Etc/UTC Etc/UCT +L Etc/UTC Etc/Universal +L Etc/UTC Etc/Zulu +L Europe/London GB +L Europe/London GB-Eire +L Etc/GMT GMT+0 +L Etc/GMT GMT-0 +L Etc/GMT GMT0 +L Etc/GMT Greenwich +L Asia/Hong_Kong Hongkong +L Africa/Abidjan Iceland +L Asia/Tehran Iran +L Asia/Jerusalem Israel +L America/Jamaica Jamaica +L Asia/Tokyo Japan +L Pacific/Kwajalein Kwajalein +L Africa/Tripoli Libya +L America/Tijuana Mexico/BajaNorte +L America/Mazatlan Mexico/BajaSur +L America/Mexico_City Mexico/General +L Pacific/Auckland NZ +L Pacific/Chatham NZ-CHAT +L America/Denver Navajo +L Asia/Shanghai PRC +L Europe/Warsaw Poland +L Europe/Lisbon Portugal +L Asia/Taipei ROC +L Asia/Seoul ROK +L Asia/Singapore Singapore +L Europe/Istanbul Turkey +L Etc/UTC UCT +L America/Anchorage US/Alaska +L America/Adak US/Aleutian +L America/Phoenix US/Arizona +L America/Chicago US/Central +L America/Indiana/Indianapolis US/East-Indiana +L America/New_York US/Eastern +L Pacific/Honolulu US/Hawaii +L America/Indiana/Knox US/Indiana-Starke +L America/Detroit US/Michigan +L America/Denver US/Mountain +L America/Los_Angeles US/Pacific +L Pacific/Pago_Pago US/Samoa +L Etc/UTC UTC +L Etc/UTC Universal +L Europe/Moscow W-SU +L Etc/UTC Zulu +L America/Argentina/Buenos_Aires America/Buenos_Aires +L America/Argentina/Catamarca America/Catamarca +L America/Argentina/Cordoba America/Cordoba +L America/Indiana/Indianapolis America/Indianapolis +L America/Argentina/Jujuy America/Jujuy +L America/Indiana/Knox America/Knox_IN +L America/Kentucky/Louisville America/Louisville +L America/Argentina/Mendoza America/Mendoza +L America/Puerto_Rico America/Virgin +L Pacific/Pago_Pago Pacific/Samoa +L Africa/Abidjan Africa/Accra +L Africa/Nairobi Africa/Addis_Ababa +L Africa/Nairobi Africa/Asmara +L Africa/Abidjan Africa/Bamako +L Africa/Lagos Africa/Bangui +L Africa/Abidjan Africa/Banjul +L Africa/Maputo Africa/Blantyre +L Africa/Lagos Africa/Brazzaville +L Africa/Maputo Africa/Bujumbura +L Africa/Abidjan Africa/Conakry +L Africa/Abidjan Africa/Dakar +L Africa/Nairobi Africa/Dar_es_Salaam +L Africa/Nairobi Africa/Djibouti +L Africa/Lagos Africa/Douala +L Africa/Abidjan Africa/Freetown +L Africa/Maputo Africa/Gaborone +L Africa/Maputo Africa/Harare +L Africa/Nairobi Africa/Kampala +L Africa/Maputo Africa/Kigali +L Africa/Lagos Africa/Kinshasa +L Africa/Lagos Africa/Libreville +L Africa/Abidjan Africa/Lome +L Africa/Lagos Africa/Luanda +L Africa/Maputo Africa/Lubumbashi +L Africa/Maputo Africa/Lusaka +L Africa/Lagos Africa/Malabo +L Africa/Johannesburg Africa/Maseru +L Africa/Johannesburg Africa/Mbabane +L Africa/Nairobi Africa/Mogadishu +L Africa/Lagos Africa/Niamey +L Africa/Abidjan Africa/Nouakchott +L Africa/Abidjan Africa/Ouagadougou +L Africa/Lagos Africa/Porto-Novo +L America/Puerto_Rico America/Anguilla +L America/Puerto_Rico America/Antigua +L America/Puerto_Rico America/Aruba +L America/Panama America/Atikokan +L America/Puerto_Rico America/Blanc-Sablon +L America/Panama America/Cayman +L America/Phoenix America/Creston +L America/Puerto_Rico America/Curacao +L America/Puerto_Rico America/Dominica +L America/Puerto_Rico America/Grenada +L America/Puerto_Rico America/Guadeloupe +L America/Puerto_Rico America/Kralendijk +L America/Puerto_Rico America/Lower_Princes +L America/Puerto_Rico America/Marigot +L America/Puerto_Rico America/Montserrat +L America/Toronto America/Nassau +L America/Puerto_Rico America/Port_of_Spain +L America/Puerto_Rico America/St_Barthelemy +L America/Puerto_Rico America/St_Kitts +L America/Puerto_Rico America/St_Lucia +L America/Puerto_Rico America/St_Thomas +L America/Puerto_Rico America/St_Vincent +L America/Puerto_Rico America/Tortola +L Pacific/Port_Moresby Antarctica/DumontDUrville +L Pacific/Auckland Antarctica/McMurdo +L Asia/Riyadh Antarctica/Syowa +L Asia/Urumqi Antarctica/Vostok +L Europe/Berlin Arctic/Longyearbyen +L Asia/Riyadh Asia/Aden +L Asia/Qatar Asia/Bahrain +L Asia/Kuching Asia/Brunei +L Asia/Singapore Asia/Kuala_Lumpur +L Asia/Riyadh Asia/Kuwait +L Asia/Dubai Asia/Muscat +L Asia/Bangkok Asia/Phnom_Penh +L Asia/Bangkok Asia/Vientiane +L Africa/Abidjan Atlantic/Reykjavik +L Africa/Abidjan Atlantic/St_Helena +L Europe/Brussels Europe/Amsterdam +L Europe/Prague Europe/Bratislava +L Europe/Zurich Europe/Busingen +L Europe/Berlin Europe/Copenhagen +L Europe/London Europe/Guernsey +L Europe/London Europe/Isle_of_Man +L Europe/London Europe/Jersey +L Europe/Belgrade Europe/Ljubljana +L Europe/Brussels Europe/Luxembourg +L Europe/Helsinki Europe/Mariehamn +L Europe/Paris Europe/Monaco +L Europe/Berlin Europe/Oslo +L Europe/Belgrade Europe/Podgorica +L Europe/Rome Europe/San_Marino +L Europe/Belgrade Europe/Sarajevo +L Europe/Belgrade Europe/Skopje +L Europe/Berlin Europe/Stockholm +L Europe/Zurich Europe/Vaduz +L Europe/Rome Europe/Vatican +L Europe/Belgrade Europe/Zagreb +L Africa/Nairobi Indian/Antananarivo +L Asia/Bangkok Indian/Christmas +L Asia/Yangon Indian/Cocos +L Africa/Nairobi Indian/Comoro +L Indian/Maldives Indian/Kerguelen +L Asia/Dubai Indian/Mahe +L Africa/Nairobi Indian/Mayotte +L Asia/Dubai Indian/Reunion +L Pacific/Port_Moresby Pacific/Chuuk +L Pacific/Tarawa Pacific/Funafuti +L Pacific/Tarawa Pacific/Majuro +L Pacific/Pago_Pago Pacific/Midway +L Pacific/Guadalcanal Pacific/Pohnpei +L Pacific/Guam Pacific/Saipan +L Pacific/Tarawa Pacific/Wake +L Pacific/Tarawa Pacific/Wallis +L Africa/Abidjan Africa/Timbuktu +L America/Argentina/Catamarca America/Argentina/ComodRivadavia +L America/Adak America/Atka +L America/Panama America/Coral_Harbour +L America/Tijuana America/Ensenada +L America/Indiana/Indianapolis America/Fort_Wayne +L America/Toronto America/Montreal +L America/Toronto America/Nipigon +L America/Rio_Branco America/Porto_Acre +L America/Winnipeg America/Rainy_River +L America/Argentina/Cordoba America/Rosario +L America/Tijuana America/Santa_Isabel +L America/Denver America/Shiprock +L America/Toronto America/Thunder_Bay +L Pacific/Auckland Antarctica/South_Pole +L Asia/Shanghai Asia/Chongqing +L Asia/Shanghai Asia/Harbin +L Asia/Urumqi Asia/Kashgar +L Asia/Jerusalem Asia/Tel_Aviv +L Europe/Berlin Atlantic/Jan_Mayen +L Australia/Sydney Australia/Canberra +L Australia/Hobart Australia/Currie +L Europe/London Europe/Belfast +L Europe/Chisinau Europe/Tiraspol +L Europe/Kyiv Europe/Uzhgorod +L Europe/Kyiv Europe/Zaporozhye +L Pacific/Kanton Pacific/Enderbury +L Pacific/Honolulu Pacific/Johnston +L Pacific/Port_Moresby Pacific/Yap +L Africa/Nairobi Africa/Asmera +L America/Nuuk America/Godthab +L Asia/Ashgabat Asia/Ashkhabad +L Asia/Kolkata Asia/Calcutta +L Asia/Shanghai Asia/Chungking +L Asia/Dhaka Asia/Dacca +L Europe/Istanbul Asia/Istanbul +L Asia/Kathmandu Asia/Katmandu +L Asia/Macau Asia/Macao +L Asia/Yangon Asia/Rangoon +L Asia/Ho_Chi_Minh Asia/Saigon +L Asia/Thimphu Asia/Thimbu +L Asia/Makassar Asia/Ujung_Pandang +L Asia/Ulaanbaatar Asia/Ulan_Bator +L Atlantic/Faroe Atlantic/Faeroe +L Europe/Kyiv Europe/Kiev +L Asia/Nicosia Europe/Nicosia +L Pacific/Guadalcanal Pacific/Ponape +L Pacific/Port_Moresby Pacific/Truk diff --git a/libs/common/pytz/zoneinfo/zone.tab b/libs/common/pytz/zoneinfo/zone.tab index dcb6e1da..2636e21a 100644 --- a/libs/common/pytz/zoneinfo/zone.tab +++ b/libs/common/pytz/zoneinfo/zone.tab @@ -3,7 +3,7 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2018-06-27): +# From Paul Eggert (2021-09-20): # This file is intended as a backward-compatibility aid for older programs. # New programs should use zone1970.tab. This file is like zone1970.tab (see # zone1970.tab's comments), but with the following additional restrictions: @@ -16,6 +16,9 @@ # clocks have agreed since 1970; this is a narrower definition than # that of zone1970.tab. # +# Unlike zone1970.tab, a row's third column can be a Link from +# 'backward' instead of a Zone. +# # This table is intended as an aid for users, to help them select timezones # appropriate for their practical needs. It is not intended to take or # endorse any position on legal or territorial claims. @@ -56,8 +59,7 @@ AS -1416-17042 Pacific/Pago_Pago AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania (most areas) -AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -4253+14719 Australia/Hobart Tasmania AU -3749+14458 Australia/Melbourne Victoria AU -3352+15113 Australia/Sydney New South Wales (most areas) AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) @@ -112,13 +114,10 @@ CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) @@ -128,11 +127,11 @@ CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4906-11631 America/Creston MST - BC (Creston) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +6043-13503 America/Whitehorse MST - Yukon (east) +CA +6404-13925 America/Dawson MST - Yukon (west) CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CA +6043-13503 America/Whitehorse Pacific - Yukon (south) -CA +6404-13925 America/Dawson Pacific - Yukon (north) CC -1210+09655 Indian/Cocos CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) @@ -189,7 +188,7 @@ GF +0456-05220 America/Cayenne GG +492717-0023210 Europe/Guernsey GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Godthab Greenland (most areas) +GL +6411-05144 America/Nuuk Greenland (most areas) GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -229,7 +228,7 @@ KE -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek KH +1133+10455 Asia/Phnom_Penh KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KM -1141+04316 Indian/Comoro KN +1718-06243 America/St_Kitts @@ -239,6 +238,7 @@ KW +2920+04759 Asia/Kuwait KY +1918-08123 America/Cayman KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev @@ -331,9 +331,12 @@ RO +4426+02606 Europe/Bucharest RS +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -RU +4457+03406 Europe/Simferopol MSK+00 - Crimea -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +# The obsolescent zone.tab format cannot represent Europe/Simferopol well. +# Put it in RU section and list as UA. See "territorial claims" above. +# Programs should use zone1970.tab instead; see above. +UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan RU +5134+04602 Europe/Saratov MSK+01 - Saratov RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk @@ -388,15 +391,13 @@ TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili TM +3757+05823 Asia/Ashgabat TN +3648+01011 Africa/Tunis -TO -2110-17510 Pacific/Tongatapu +TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TT +1039-06131 America/Port_of_Spain TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Ruthenia -UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) +UA +5026+03031 Europe/Kyiv Ukraine (most areas) UG +0019+03225 Africa/Kampala UM +2813-17722 Pacific/Midway Midway Islands UM +1917+16637 Pacific/Wake Wake Island diff --git a/libs/common/pytz/zoneinfo/zone1970.tab b/libs/common/pytz/zoneinfo/zone1970.tab index 7c86fb69..75372e3f 100644 --- a/libs/common/pytz/zoneinfo/zone1970.tab +++ b/libs/common/pytz/zoneinfo/zone1970.tab @@ -34,19 +34,16 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM +2518+05518 Asia/Dubai +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai UAE, Oman, Réunion, Seychelles, Crozet, Scattered Is AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan AQ -6617+11031 Antarctica/Casey Casey AQ -6835+07758 Antarctica/Davis Davis -AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville AQ -6736+06253 Antarctica/Mawson Mawson AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera -AQ -690022+0393524 Antarctica/Syowa Syowa AQ -720041+0023206 Antarctica/Troll Troll -AQ -7824+10654 Antarctica/Vostok Vostok AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) @@ -63,8 +60,7 @@ AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania (most areas) -AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -4253+14719 Australia/Hobart Tasmania AU -3749+14458 Australia/Melbourne Victoria AU -3352+15113 Australia/Sydney New South Wales (most areas) AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) @@ -77,10 +73,9 @@ AU -3143+12852 Australia/Eucla Western Australia (Eucla) AZ +4023+04951 Asia/Baku BB +1306-05937 America/Barbados BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels +BE,LU,NL +5050+00420 Europe/Brussels BG +4241+02319 Europe/Sofia BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei BO -1630-06809 America/La_Paz BR -0351-03225 America/Noronha Atlantic islands BR -0127-04829 America/Belem Pará (east); Amapá @@ -98,7 +93,6 @@ BR +0249-06040 America/Boa_Vista Roraima BR -0308-06001 America/Manaus Amazonas (east) BR -0640-06952 America/Eirunepe Amazonas (west) BR -0958-06748 America/Rio_Branco Acre -BS +2505-07721 America/Nassau BT +2728+08939 Asia/Thimphu BY +5354+02734 Europe/Minsk BZ +1730-08812 America/Belize @@ -107,15 +101,10 @@ CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) -CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) -CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) @@ -124,32 +113,27 @@ CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) -CA +4906-11631 America/Creston MST - BC (Creston) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +6043-13503 America/Whitehorse MST - Yukon (east) +CA +6404-13925 America/Dawson MST - Yukon (west) CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CA +6043-13503 America/Whitehorse Pacific - Yukon (south) -CA +6404-13925 America/Dawson Pacific - Yukon (north) -CC -1210+09655 Indian/Cocos CH,DE,LI +4723+00832 Europe/Zurich Swiss time -CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan +CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga CL -3327-07040 America/Santiago Chile (most areas) CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time +CN,AQ +4348+08735 Asia/Urumqi Xinjiang Time, Vostok CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde -CW,AW,BQ,SX +1211-06900 America/Curacao -CX -1025+10543 Indian/Christmas CY +3510+03322 Asia/Nicosia Cyprus (most areas) CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DK +5540+01235 Europe/Copenhagen +DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin Germany (most areas), Scandinavia DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) @@ -163,17 +147,14 @@ ES +2806-01524 Atlantic/Canary Canary Islands FI,AX +6010+02458 Europe/Helsinki FJ -1808+17825 Pacific/Fiji FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape FM +0519+16259 Pacific/Kosrae Kosrae FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris +FR,MC +4852+00220 Europe/Paris GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne -GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Godthab Greenland (most areas) +GL +6411-05144 America/Nuuk Greenland (most areas) GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -197,20 +178,20 @@ IN +2232+08822 Asia/Kolkata IO -0720+07225 Indian/Chagos IQ +3321+04425 Asia/Baghdad IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik IT,SM,VA +4154+01229 Europe/Rome JM +175805-0764736 America/Jamaica JO +3157+03556 Asia/Amman JP +353916+1394441 Asia/Tokyo KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek -KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Tuvalu, Wallis & Futuna, Wake +KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev @@ -219,15 +200,12 @@ LB +3353+03530 Asia/Beirut LK +0656+07951 Asia/Colombo LR +0618-01047 Africa/Monrovia LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg LV +5657+02406 Europe/Riga LY +3254+01311 Africa/Tripoli MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco MD +4700+02850 Europe/Chisinau -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) MH +0905+16720 Pacific/Kwajalein Kwajalein -MM +1647+09610 Asia/Yangon +MM,CC +1647+09610 Asia/Yangon MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar @@ -235,7 +213,7 @@ MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives +MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I MX +1924-09909 America/Mexico_City Central Time MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo MX +2058-08937 America/Merida Central Time - Campeche, Yucatán @@ -247,34 +225,31 @@ MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora MX +3232-11701 America/Tijuana Pacific Time US - Baja California MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak +MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea NF -2903+16758 Pacific/Norfolk NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO,SJ +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ,AQ -3652+17446 Pacific/Auckland New Zealand time NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,KY +0858-07932 America/Panama +PA,CA,KY +0858-07932 America/Panama EST - Panama, Cayman, ON (Atikokan), NU (Coral H) PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d'Urville PG -0613+15534 Pacific/Bougainville Bougainville PH +1435+12100 Asia/Manila PK +2452+06703 Asia/Karachi PL +5215+02100 Europe/Warsaw PM +4703-05620 America/Miquelon PN -2504-13005 Pacific/Pitcairn -PR +182806-0660622 America/Puerto_Rico +PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST PS +3130+03428 Asia/Gaza Gaza Strip PS +313200+0350542 Asia/Hebron West Bank PT +3843-00908 Europe/Lisbon Portugal (mainland) @@ -283,14 +258,14 @@ PT +3744-02540 Atlantic/Azores Azores PW +0720+13429 Pacific/Palau PY -2516-05740 America/Asuncion QA,BH +2517+05132 Asia/Qatar -RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands RO +4426+02606 Europe/Bucharest RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -RU +4457+03406 Europe/Simferopol MSK+00 - Crimea -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +# Mention RU and UA alphabetically. See "territorial claims" above. +RU,UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan RU +5134+04602 Europe/Saratov MSK+01 - Saratov RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk @@ -313,12 +288,10 @@ RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,KW,YE +2438+04643 Asia/Riyadh -SB -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe +SA,AQ,KW,YE +2438+04643 Asia/Riyadh Arabia, Syowa +SB,FM -0932+16012 Pacific/Guadalcanal Solomons, Pohnpei SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm -SG +0117+10351 Asia/Singapore +SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome @@ -326,22 +299,16 @@ SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island -TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili TM +3757+05823 Asia/Ashgabat TN +3648+01011 Africa/Tunis -TO -2110-17510 Pacific/Tongatapu +TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul -TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain -TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Ruthenia -UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) -UM +1917+16637 Pacific/Wake Wake Island +UA +5026+03031 Europe/Kyiv Ukraine (most areas) US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) @@ -361,7 +328,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US,CA +332654-1120424 America/Phoenix MST - Arizona (except Navajo), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -377,6 +344,29 @@ UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg +# +# The next section contains experimental tab-separated comments for +# use by user agents like tzselect that identify continents and oceans. +# +# For example, the comment "#@AQAntarctica/" means the country code +# AQ is in the continent Antarctica regardless of the Zone name, +# so Pacific/Auckland should be listed under Antarctica as well as +# under the Pacific because its line's country codes include AQ. +# +# If more than one country code is affected each is listed separated +# by commas, e.g., #@IS,SHAtlantic/". If a country code is in +# more than one continent or ocean, each is listed separated by +# commas, e.g., the second column of "#@CY,TRAsia/,Europe/". +# +# These experimental comments are present only for country codes where +# the continent or ocean is not already obvious from the Zone name. +# For example, there is no such comment for RU since it already +# corresponds to Zone names starting with both "Europe/" and "Asia/". +# +#@AQ Antarctica/ +#@IS,SH Atlantic/ +#@CY,TR Asia/,Europe/ +#@SJ Arctic/ +#@CC,CX,KM,MG,YT Indian/ diff --git a/libs/common/rarfile.py b/libs/common/rarfile.py index 78148c19..2ff5af49 100644 --- a/libs/common/rarfile.py +++ b/libs/common/rarfile.py @@ -1,6 +1,6 @@ # rarfile.py # -# Copyright (c) 2005-2016 Marko Kreen +# Copyright (c) 2005-2019 Marko Kreen # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -170,8 +170,17 @@ else: # pragma: no cover unicode = str _byte_code = int # noqa +# don't break 2.6 completely +if sys.hexversion < 0x2070000: + memoryview = lambda x: x # noqa -__version__ = '3.0' +try: + from pathlib import Path + _have_pathlib = True +except ImportError: + _have_pathlib = False + +__version__ = '3.1' # export only interesting items __all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] @@ -215,6 +224,12 @@ ALT_EXTRACT_ARGS = ('-x', '-f') ALT_TEST_ARGS = ('-t', '-f') ALT_CHECK_ARGS = ('--help',) +#ALT_TOOL = 'unar' +#ALT_OPEN_ARGS = ('-o', '-') +#ALT_EXTRACT_ARGS = () +#ALT_TEST_ARGS = ('-test',) # does not work +#ALT_CHECK_ARGS = ('-v',) + #: whether to speed up decompression by using tmp archive USE_EXTRACT_HACK = 1 @@ -646,7 +661,11 @@ class RarFile(object): Either "stop" to quietly stop parsing on errors, or "strict" to raise errors. Default is "stop". """ - self._rarfile = rarfile + if _have_pathlib and isinstance(rarfile, Path): + self._rarfile = str(rarfile) + else: + self._rarfile = rarfile + self._charset = charset or DEFAULT_CHARSET self._info_callback = info_callback self._crc_check = crc_check @@ -794,6 +813,8 @@ class RarFile(object): """ if isinstance(member, RarInfo): fname = member.filename + elif _have_pathlib and isinstance(member, Path): + fname = str(member) else: fname = member self._extract([fname], path, pwd) @@ -879,6 +900,8 @@ class RarFile(object): # destination path if path is not None: + if _have_pathlib and isinstance(path, Path): + path = str(path) cmd.append(path + os.sep) # call @@ -948,6 +971,8 @@ class CommonParser(object): """ if isinstance(member, RarInfo): fname = member.filename + elif _have_pathlib and isinstance(member, Path): + fname = str(member) else: fname = member @@ -1024,6 +1049,12 @@ class CommonParser(object): # RAR 2.x does not set FIRSTVOLUME, # so check it only if NEWNUMBERING is used if (h.flags & RAR_MAIN_FIRSTVOLUME) == 0: + if getattr(h, 'main_volume_number', None) is not None: + # rar5 may have more info + raise NeedFirstVolume( + "Need to start from first volume (current: %r)" + % (h.main_volume_number,) + ) raise NeedFirstVolume("Need to start from first volume") if h.flags & RAR_MAIN_PASSWORD: self._needs_password = True @@ -1072,7 +1103,7 @@ class CommonParser(object): # handle encrypted headers if (self._main and self._main.flags & RAR_MAIN_PASSWORD) or self._hdrenc_main: if not self._password: - return + return None fd = self._decrypt_header(fd) # now read actual header @@ -1673,7 +1704,7 @@ class RAR5Parser(CommonParser): def _parse_main_block(self, h, hdata, pos): h.main_flags, pos = load_vint(hdata, pos) if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR: - h.main_volume_number = load_vint(hdata, pos) + h.main_volume_number, pos = load_vint(hdata, pos) h.flags |= RAR_MAIN_NEWNUMBERING if h.main_flags & RAR5_MAIN_FLAG_SOLID: @@ -1874,6 +1905,7 @@ class RAR5Parser(CommonParser): # rar bug? - appends zero to comment cmt = cmt.split(ZERO, 1)[0] self.comment = cmt.decode('utf8') + return None def _open_hack(self, inf, psw): # len, type, blk_flags, flags @@ -2033,6 +2065,7 @@ class RarExtFile(RawIOBase): def _read(self, cnt): """Actual read that gets sanitized cnt.""" + raise NotImplementedError("_read") def close(self): """Close open resources.""" @@ -2525,6 +2558,53 @@ class Blake2SP(object): """Hexadecimal digest.""" return tohex(self.digest()) + +class Rar3Sha1(object): + """Bug-compat for SHA1 + """ + digest_size = 20 + block_size = 64 + + _BLK_BE = struct.Struct(b'>16L') + _BLK_LE = struct.Struct(b'<16L') + + __slots__ = ('_nbytes', '_md', '_rarbug') + + def __init__(self, data=b'', rarbug=False): + self._md = sha1() + self._nbytes = 0 + self._rarbug = rarbug + self.update(data) + + def update(self, data): + """Process more data.""" + self._md.update(data) + bufpos = self._nbytes & 63 + self._nbytes += len(data) + + if self._rarbug and len(data) > 64: + dpos = self.block_size - bufpos + while dpos + self.block_size <= len(data): + self._corrupt(data, dpos) + dpos += self.block_size + + def digest(self): + """Return final state.""" + return self._md.digest() + + def hexdigest(self): + """Return final state as hex string.""" + return self._md.hexdigest() + + def _corrupt(self, data, dpos): + """Corruption from SHA1 core.""" + ws = list(self._BLK_BE.unpack_from(data, dpos)) + for t in range(16, 80): + tmp = ws[(t - 3) & 15] ^ ws[(t - 8) & 15] ^ ws[(t - 14) & 15] ^ ws[(t - 16) & 15] + ws[t & 15] = ((tmp << 1) | (tmp >> (32 - 1))) & 0xFFFFFFFF + self._BLK_LE.pack_into(data, dpos, *ws) + + ## ## Utility functions ## @@ -2672,7 +2752,12 @@ def _parse_xtime(flag, data, pos, basetime=None): def is_filelike(obj): """Filename or file object? """ - if isinstance(obj, str) or isinstance(obj, unicode): + if _have_pathlib: + filename_types = (bytes, unicode, Path) + else: + filename_types = (bytes, unicode) + + if isinstance(obj, filename_types): return False res = True for a in ('read', 'tell', 'seek'): @@ -2686,13 +2771,14 @@ def rar3_s2k(psw, salt): """ if not isinstance(psw, unicode): psw = psw.decode('utf8') - seed = psw.encode('utf-16le') + salt + seed = bytearray(psw.encode('utf-16le') + salt) + h = Rar3Sha1(rarbug=True) iv = EMPTY - h = sha1() for i in range(16): for j in range(0x4000): cnt = S_LONG.pack(i * 0x4000 + j) - h.update(seed + cnt[:3]) + h.update(seed) + h.update(cnt[:3]) if j == 0: iv += h.digest()[19:20] key_be = h.digest()[:16] @@ -2814,6 +2900,8 @@ def custom_popen(cmd): except OSError as ex: if ex.errno == errno.ENOENT: raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL) + if ex.errno == errno.EACCES or ex.errno == errno.EPERM: + raise RarCannotExec("Cannot execute unrar (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL) raise return p @@ -2945,7 +3033,8 @@ def _check_unrar_tool(): TEST_ARGS = ALT_TEST_ARGS except RarCannotExec: # no usable tool, only uncompressed archives work - pass + return False + return True _check_unrar_tool() diff --git a/libs/common/stevedore/__init__.py b/libs/common/stevedore/__init__.py index a471f31d..fdf37a99 100644 --- a/libs/common/stevedore/__init__.py +++ b/libs/common/stevedore/__init__.py @@ -21,4 +21,3 @@ import logging LOG = logging.getLogger('stevedore') LOG.addHandler(logging.NullHandler()) - diff --git a/libs/common/stevedore/_cache.py b/libs/common/stevedore/_cache.py new file mode 100644 index 00000000..d0574f53 --- /dev/null +++ b/libs/common/stevedore/_cache.py @@ -0,0 +1,203 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Use a cache layer in front of entry point scanning.""" + +import errno +import glob +import hashlib +import itertools +import json +import logging +import os +import os.path +import struct +import sys + +try: + # For python 3.8 and later + import importlib.metadata as importlib_metadata +except ImportError: + # For everyone else + import importlib_metadata + + +log = logging.getLogger('stevedore._cache') + + +def _get_cache_dir(): + """Locate a platform-appropriate cache directory to use. + + Does not ensure that the cache directory exists. + """ + # Linux, Unix, AIX, etc. + if os.name == 'posix' and sys.platform != 'darwin': + # use ~/.cache if empty OR not set + base_path = os.environ.get("XDG_CACHE_HOME", None) \ + or os.path.expanduser('~/.cache') + return os.path.join(base_path, 'python-entrypoints') + + # Mac OS + elif sys.platform == 'darwin': + return os.path.expanduser('~/Library/Caches/Python Entry Points') + + # Windows (hopefully) + else: + base_path = os.environ.get('LOCALAPPDATA', None) \ + or os.path.expanduser('~\\AppData\\Local') + return os.path.join(base_path, 'Python Entry Points') + + +def _get_mtime(name): + try: + s = os.stat(name) + return s.st_mtime + except OSError as err: + if err.errno != errno.ENOENT: + raise + return -1.0 + + +def _ftobytes(f): + return struct.Struct('f').pack(f) + + +def _hash_settings_for_path(path): + """Return a hash and the path settings that created it.""" + paths = [] + h = hashlib.sha256() + + # Tie the cache to the python interpreter, in case it is part of a + # virtualenv. + h.update(sys.executable.encode('utf-8')) + h.update(sys.prefix.encode('utf-8')) + + for entry in path: + mtime = _get_mtime(entry) + h.update(entry.encode('utf-8')) + h.update(_ftobytes(mtime)) + paths.append((entry, mtime)) + + for ep_file in itertools.chain( + glob.iglob(os.path.join(entry, + '*.dist-info', + 'entry_points.txt')), + glob.iglob(os.path.join(entry, + '*.egg-info', + 'entry_points.txt')) + ): + mtime = _get_mtime(ep_file) + h.update(ep_file.encode('utf-8')) + h.update(_ftobytes(mtime)) + paths.append((ep_file, mtime)) + + return (h.hexdigest(), paths) + + +def _build_cacheable_data(path): + real_groups = importlib_metadata.entry_points() + # Convert the namedtuple values to regular tuples + groups = {} + for name, group_data in real_groups.items(): + existing = set() + members = [] + groups[name] = members + for ep in group_data: + # Filter out duplicates that can occur when testing a + # package that provides entry points using tox, where the + # package is installed in the virtualenv that tox builds + # and is present in the path as '.'. + item = ep.name, ep.value, ep.group # convert to tuple + if item in existing: + continue + existing.add(item) + members.append(item) + return { + 'groups': groups, + 'sys.executable': sys.executable, + 'sys.prefix': sys.prefix, + } + + +class Cache: + + def __init__(self, cache_dir=None): + if cache_dir is None: + cache_dir = _get_cache_dir() + self._dir = cache_dir + self._internal = {} + self._disable_caching = False + + # Caching can be disabled by either placing .disable file into the + # target directory or when python executable is under /tmp (this is the + # case when executed from ansible) + if any([os.path.isfile(os.path.join(self._dir, '.disable')), + sys.executable[0:4] == '/tmp']): + self._disable_caching = True + + def _get_data_for_path(self, path): + if path is None: + path = sys.path + + internal_key = tuple(path) + if internal_key in self._internal: + return self._internal[internal_key] + + digest, path_values = _hash_settings_for_path(path) + filename = os.path.join(self._dir, digest) + try: + log.debug('reading %s', filename) + with open(filename, 'r') as f: + data = json.load(f) + except (IOError, json.JSONDecodeError): + data = _build_cacheable_data(path) + data['path_values'] = path_values + if not self._disable_caching: + try: + log.debug('writing to %s', filename) + os.makedirs(self._dir, exist_ok=True) + with open(filename, 'w') as f: + json.dump(data, f) + except (IOError, OSError): + # Could not create cache dir or write file. + pass + + self._internal[internal_key] = data + return data + + def get_group_all(self, group, path=None): + result = [] + data = self._get_data_for_path(path) + group_data = data.get('groups', {}).get(group, []) + for vals in group_data: + result.append(importlib_metadata.EntryPoint(*vals)) + return result + + def get_group_named(self, group, path=None): + result = {} + for ep in self.get_group_all(group, path=path): + if ep.name not in result: + result[ep.name] = ep + return result + + def get_single(self, group, name, path=None): + for name, ep in self.get_group_named(group, path=path).items(): + if name == name: + return ep + raise ValueError('No entrypoint {!r} in group {!r}'.format( + group, name)) + + +_c = Cache() +get_group_all = _c.get_group_all +get_group_named = _c.get_group_named +get_single = _c.get_single diff --git a/libs/common/stevedore/dispatch.py b/libs/common/stevedore/dispatch.py index a1589673..27b511fc 100644 --- a/libs/common/stevedore/dispatch.py +++ b/libs/common/stevedore/dispatch.py @@ -142,7 +142,7 @@ class NameDispatchExtensionManager(DispatchExtensionManager): then ignored :type invoke_on_load: bool :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function diff --git a/libs/common/stevedore/driver.py b/libs/common/stevedore/driver.py index 167dc671..bccbcd19 100644 --- a/libs/common/stevedore/driver.py +++ b/libs/common/stevedore/driver.py @@ -10,7 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from .exception import NoMatches, MultipleMatches +from .exception import MultipleMatches +from .exception import NoMatches from .named import NamedExtensionManager @@ -33,7 +34,7 @@ class DriverManager(NamedExtensionManager): is True. :type invoke_kwds: dict :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function @@ -85,7 +86,7 @@ class DriverManager(NamedExtensionManager): and then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will - be called when a entrypoint can not be loaded. The + be called when an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) @@ -142,7 +143,6 @@ class DriverManager(NamedExtensionManager): @property def driver(self): - """Returns the driver being used by this manager. - """ + """Returns the driver being used by this manager.""" ext = self.extensions[0] return ext.obj if ext.obj else ext.plugin diff --git a/libs/common/stevedore/enabled.py b/libs/common/stevedore/enabled.py index c2e0c03d..7c12499b 100644 --- a/libs/common/stevedore/enabled.py +++ b/libs/common/stevedore/enabled.py @@ -46,7 +46,7 @@ class EnabledExtensionManager(ExtensionManager): then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function diff --git a/libs/common/stevedore/example/base.py b/libs/common/stevedore/example/base.py index ec95424e..cd48bf21 100644 --- a/libs/common/stevedore/example/base.py +++ b/libs/common/stevedore/example/base.py @@ -1,10 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. import abc -import six - -@six.add_metaclass(abc.ABCMeta) -class FormatterBase(object): +class FormatterBase(metaclass=abc.ABCMeta): """Base class for example plugin used in the tutorial. """ diff --git a/libs/common/stevedore/example/load_as_driver.py b/libs/common/stevedore/example/load_as_driver.py index d8c47f5f..a3de5df2 100644 --- a/libs/common/stevedore/example/load_as_driver.py +++ b/libs/common/stevedore/example/load_as_driver.py @@ -1,5 +1,17 @@ -from __future__ import print_function - +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. import argparse from stevedore import driver diff --git a/libs/common/stevedore/example/load_as_extension.py b/libs/common/stevedore/example/load_as_extension.py index 436206a3..1af1f46c 100644 --- a/libs/common/stevedore/example/load_as_extension.py +++ b/libs/common/stevedore/example/load_as_extension.py @@ -1,5 +1,17 @@ -from __future__ import print_function - +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. import argparse from stevedore import extension diff --git a/libs/common/stevedore/example/setup.py b/libs/common/stevedore/example/setup.py index 702e6d4d..c0ea667b 100644 --- a/libs/common/stevedore/example/setup.py +++ b/libs/common/stevedore/example/setup.py @@ -1,4 +1,19 @@ -from setuptools import setup, find_packages +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from setuptools import find_packages +from setuptools import setup setup( name='stevedore-examples', @@ -9,7 +24,7 @@ setup( author='Doug Hellmann', author_email='doug@doughellmann.com', - url='http://git.openstack.org/cgit/openstack/stevedore', + url='http://opendev.org/openstack/stevedore', classifiers=['Development Status :: 3 - Alpha', 'License :: OSI Approved :: Apache Software License', diff --git a/libs/common/stevedore/example/simple.py b/libs/common/stevedore/example/simple.py index 1cad96af..0cc3acd9 100644 --- a/libs/common/stevedore/example/simple.py +++ b/libs/common/stevedore/example/simple.py @@ -1,9 +1,21 @@ +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from stevedore.example import base class Simple(base.FormatterBase): - """A very basic formatter. - """ + """A very basic formatter.""" def format(self, data): """Format the data and return unicode text. diff --git a/libs/common/stevedore/example2/fields.py b/libs/common/stevedore/example2/fields.py index f5c8e194..82db7474 100644 --- a/libs/common/stevedore/example2/fields.py +++ b/libs/common/stevedore/example2/fields.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. import textwrap from stevedore.example import base diff --git a/libs/common/stevedore/example2/setup.py b/libs/common/stevedore/example2/setup.py index bd23838a..2293c661 100644 --- a/libs/common/stevedore/example2/setup.py +++ b/libs/common/stevedore/example2/setup.py @@ -1,4 +1,19 @@ -from setuptools import setup, find_packages +# Copyright (C) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from setuptools import find_packages +from setuptools import setup setup( name='stevedore-examples2', @@ -9,7 +24,7 @@ setup( author='Doug Hellmann', author_email='doug@doughellmann.com', - url='http://git.openstack.org/cgit/openstack/stevedore', + url='http://opendev.org/openstack/stevedore', classifiers=['Development Status :: 3 - Alpha', 'License :: OSI Approved :: Apache Software License', diff --git a/libs/common/stevedore/extension.py b/libs/common/stevedore/extension.py index f5c22928..06ac0678 100644 --- a/libs/common/stevedore/extension.py +++ b/libs/common/stevedore/extension.py @@ -13,11 +13,10 @@ """ExtensionManager """ -import operator -import pkg_resources - import logging +import operator +from . import _cache from .exception import NoMatches LOG = logging.getLogger(__name__) @@ -34,7 +33,7 @@ class Extension(object): :param name: The entry point name. :type name: str :param entry_point: The EntryPoint instance returned by - :mod:`pkg_resources`. + :mod:`entrypoints`. :type entry_point: EntryPoint :param plugin: The value returned by entry_point.load() :param obj: The object returned by ``plugin(*args, **kwds)`` if the @@ -48,6 +47,38 @@ class Extension(object): self.plugin = plugin self.obj = obj + @property + def module_name(self): + """The name of the module from which the entry point is loaded. + + :return: A string in 'dotted.module' format. + """ + # NOTE: importlib_metadata from PyPI includes this but the + # Python 3.8 standard library does not. + match = self.entry_point.pattern.match(self.entry_point.value) + return match.group('module') + + @property + def extras(self): + """The 'extras' settings for the plugin.""" + # NOTE: The underlying package returns re.Match objects for + # some reason. Translate those to the matched strings, which + # seem more useful. + return [ + # Python 3.6 returns _sre.SRE_Match objects. Later + # versions of python return re.Match objects. Both types + # have a 'string' attribute containing the text that + # matched the pattern. + getattr(e, 'string', e) + for e in self.entry_point.extras + ] + + @property + def attr(self): + """The attribute of the module to be loaded.""" + match = self.entry_point.pattern.match(self.entry_point.value) + return match.group('attr') + @property def entry_point_target(self): """The module and attribute referenced by this extension's entry_point. @@ -55,8 +86,7 @@ class Extension(object): :return: A string representation of the target of the entry point in 'dotted.module:object' format. """ - return '%s:%s' % (self.entry_point.module_name, - self.entry_point.attrs[0]) + return self.entry_point.value class ExtensionManager(object): @@ -80,7 +110,7 @@ class ExtensionManager(object): then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function @@ -126,7 +156,7 @@ class ExtensionManager(object): are logged and then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will - be called when a entrypoint can not be loaded. The + be called when an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) @@ -174,7 +204,7 @@ class ExtensionManager(object): """ if self.namespace not in self.ENTRY_POINT_CACHE: - eps = list(pkg_resources.iter_entry_points(self.namespace)) + eps = list(_cache.get_group_all(self.namespace)) self.ENTRY_POINT_CACHE[self.namespace] = eps return self.ENTRY_POINT_CACHE[self.namespace] @@ -222,7 +252,7 @@ class ExtensionManager(object): ep.require() plugin = ep.resolve() else: - plugin = ep.load(require=verify_requirements) + plugin = ep.load() if invoke_on_load: obj = plugin(*invoke_args, **invoke_kwds) else: @@ -301,8 +331,7 @@ class ExtensionManager(object): LOG.exception(err) def items(self): - """ - Return an iterator of tuples of the form (name, extension). + """Return an iterator of tuples of the form (name, extension). This is analogous to the Mapping.items() method. """ @@ -326,6 +355,5 @@ class ExtensionManager(object): return self._extensions_by_name[name] def __contains__(self, name): - """Return true if name is in list of enabled extensions. - """ + """Return true if name is in list of enabled extensions.""" return any(extension.name == name for extension in self.extensions) diff --git a/libs/common/stevedore/hook.py b/libs/common/stevedore/hook.py index 014b7c3c..4225db33 100644 --- a/libs/common/stevedore/hook.py +++ b/libs/common/stevedore/hook.py @@ -32,7 +32,7 @@ class HookManager(NamedExtensionManager): is True. :type invoke_kwds: dict :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function @@ -40,8 +40,6 @@ class HookManager(NamedExtensionManager): dependencies of the plugin(s) being loaded. Defaults to False. :type verify_requirements: bool :type on_missing_entrypoints_callback: function - :param verify_requirements: Use setuptools to enforce the - dependencies of the plugin(s) being loaded. Defaults to False. :param warn_on_missing_entrypoint: Flag to control whether failing to load a plugin is reported via a log mess. Only applies if on_missing_entrypoints_callback is None. diff --git a/libs/common/stevedore/named.py b/libs/common/stevedore/named.py index 3b47dfd3..1be3922b 100644 --- a/libs/common/stevedore/named.py +++ b/libs/common/stevedore/named.py @@ -46,7 +46,7 @@ class NamedExtensionManager(ExtensionManager): then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will be called when - a entrypoint can not be loaded. The arguments that will be provided + an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function @@ -108,7 +108,7 @@ class NamedExtensionManager(ExtensionManager): and then ignored :type propagate_map_exceptions: bool :param on_load_failure_callback: Callback function that will - be called when a entrypoint can not be loaded. The + be called when an entrypoint can not be loaded. The arguments that will be provided when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) diff --git a/libs/common/stevedore/sphinxext.py b/libs/common/stevedore/sphinxext.py index 8ca88bbb..250122ea 100644 --- a/libs/common/stevedore/sphinxext.py +++ b/libs/common/stevedore/sphinxext.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import unicode_literals - import inspect from docutils import nodes @@ -36,29 +34,32 @@ def _simple_list(mgr): doc = _get_docstring(ext.plugin) or '\n' summary = doc.splitlines()[0].strip() yield('* %s -- %s' % (ext.name, summary), - ext.entry_point.module_name) + ext.module_name) def _detailed_list(mgr, over='', under='-', titlecase=False): for name in sorted(mgr.names()): ext = mgr[name] if over: - yield (over * len(ext.name), ext.entry_point.module_name) + yield (over * len(ext.name), ext.module_name) if titlecase: - yield (ext.name.title(), ext.entry_point.module_name) + yield (ext.name.title(), ext.module_name) else: - yield (ext.name, ext.entry_point.module_name) + yield (ext.name, ext.module_name) if under: - yield (under * len(ext.name), ext.entry_point.module_name) - yield ('\n', ext.entry_point.module_name) + yield (under * len(ext.name), ext.module_name) + yield ('\n', ext.module_name) doc = _get_docstring(ext.plugin) if doc: - yield (doc, ext.entry_point.module_name) + yield (doc, ext.module_name) else: - yield ('.. warning:: No documentation found in %s' - % ext.entry_point, - ext.entry_point.module_name) - yield ('\n', ext.entry_point.module_name) + yield ( + '.. warning:: No documentation found for {} in {}'.format( + ext.name, ext.entry_point_target, + ), + ext.module_name, + ) + yield ('\n', ext.module_name) class ListPluginsDirective(rst.Directive): @@ -81,7 +82,7 @@ class ListPluginsDirective(rst.Directive): underline_style = self.options.get('underline-style', '=') def report_load_failure(mgr, ep, err): - LOG.warning(u'Failed to load %s: %s' % (ep.module_name, err)) + LOG.warning(u'Failed to load %s: %s' % (ep.module, err)) mgr = extension.ExtensionManager( namespace, @@ -113,3 +114,7 @@ class ListPluginsDirective(rst.Directive): def setup(app): LOG.info('loading stevedore.sphinxext') app.add_directive('list-plugins', ListPluginsDirective) + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/libs/common/stevedore/tests/test_cache.py b/libs/common/stevedore/tests/test_cache.py new file mode 100644 index 00000000..8bf49c8f --- /dev/null +++ b/libs/common/stevedore/tests/test_cache.py @@ -0,0 +1,56 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for stevedore._cache +""" +import sys + +from unittest import mock + +from stevedore import _cache +from stevedore.tests import utils + + +class TestCache(utils.TestCase): + + def test_disable_caching_executable(self): + """Test caching is disabled if python interpreter is located under /tmp + directory (Ansible) + """ + with mock.patch.object(sys, 'executable', '/tmp/fake'): + sot = _cache.Cache() + self.assertTrue(sot._disable_caching) + + def test_disable_caching_file(self): + """Test caching is disabled if .disable file is present in target + dir + """ + cache_dir = _cache._get_cache_dir() + + with mock.patch('os.path.isfile') as mock_path: + mock_path.return_value = True + sot = _cache.Cache() + mock_path.assert_called_with('%s/.disable' % cache_dir) + self.assertTrue(sot._disable_caching) + + mock_path.return_value = False + sot = _cache.Cache() + self.assertFalse(sot._disable_caching) + + @mock.patch('os.makedirs') + @mock.patch('builtins.open') + def test__get_data_for_path_no_write(self, mock_open, mock_mkdir): + sot = _cache.Cache() + sot._disable_caching = True + mock_open.side_effect = IOError + sot._get_data_for_path('fake') + mock_mkdir.assert_not_called() diff --git a/libs/common/stevedore/tests/test_callback.py b/libs/common/stevedore/tests/test_callback.py index c513aa22..75026f75 100644 --- a/libs/common/stevedore/tests/test_callback.py +++ b/libs/common/stevedore/tests/test_callback.py @@ -12,8 +12,9 @@ """Tests for failure loading callback """ +from unittest import mock + from testtools.matchers import GreaterThan -import mock from stevedore import extension from stevedore import named diff --git a/libs/common/stevedore/tests/test_dispatch.py b/libs/common/stevedore/tests/test_dispatch.py index f1c305ab..e54e4928 100644 --- a/libs/common/stevedore/tests/test_dispatch.py +++ b/libs/common/stevedore/tests/test_dispatch.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from stevedore.tests import utils from stevedore import dispatch +from stevedore.tests import utils def check_dispatch(ep, *args, **kwds): diff --git a/libs/common/stevedore/tests/test_driver.py b/libs/common/stevedore/tests/test_driver.py index c568a3a8..92308359 100644 --- a/libs/common/stevedore/tests/test_driver.py +++ b/libs/common/stevedore/tests/test_driver.py @@ -13,7 +13,12 @@ """Tests for stevedore.extension """ -import pkg_resources +try: + # For python 3.8 and later + import importlib.metadata as importlib_metadata +except ImportError: + # For everyone else + import importlib_metadata from stevedore import driver from stevedore import exception @@ -68,13 +73,15 @@ class TestCallback(utils.TestCase): extensions = [ extension.Extension( 'backend', - pkg_resources.EntryPoint.parse('backend = pkg1:driver'), + importlib_metadata.EntryPoint( + 'backend', 'pkg1:driver', 'backend'), 'pkg backend', None, ), extension.Extension( 'backend', - pkg_resources.EntryPoint.parse('backend = pkg2:driver'), + importlib_metadata.EntryPoint( + 'backend', 'pkg2:driver', 'backend'), 'pkg backend', None, ), diff --git a/libs/common/stevedore/tests/test_extension.py b/libs/common/stevedore/tests/test_extension.py index 17f270fc..405fb88b 100644 --- a/libs/common/stevedore/tests/test_extension.py +++ b/libs/common/stevedore/tests/test_extension.py @@ -14,8 +14,14 @@ """ import operator +from unittest import mock -import mock +try: + # For python 3.8 and later + import importlib.metadata as importlib_metadata +except ImportError: + # For everyone else + import importlib_metadata from stevedore import exception from stevedore import extension @@ -97,13 +103,13 @@ class TestCallback(utils.TestCase): def test_use_cache(self): # If we insert something into the cache of entry points, - # the manager should not have to call into pkg_resources + # the manager should not have to call into entrypoints # to find the plugins. cache = extension.ExtensionManager.ENTRY_POINT_CACHE cache['stevedore.test.faux'] = [] - with mock.patch('pkg_resources.iter_entry_points', + with mock.patch('stevedore._cache.get_group_all', side_effect= - AssertionError('called iter_entry_points')): + AssertionError('called get_group_all')): em = extension.ExtensionManager('stevedore.test.faux') names = em.names() self.assertEqual(names, []) @@ -236,9 +242,48 @@ class TestLoadRequirementsOldSetuptools(utils.TestCase): def test_verify_requirements(self): self.em._load_one_plugin(self.mock_ep, False, (), {}, verify_requirements=True) - self.mock_ep.load.assert_called_once_with(require=True) + self.mock_ep.load.assert_called_once_with() def test_no_verify_requirements(self): self.em._load_one_plugin(self.mock_ep, False, (), {}, verify_requirements=False) - self.mock_ep.load.assert_called_once_with(require=False) + self.mock_ep.load.assert_called_once_with() + + +class TestExtensionProperties(utils.TestCase): + + def setUp(self): + self.ext1 = extension.Extension( + 'name', + importlib_metadata.EntryPoint( + 'name', 'module.name:attribute.name [extra]', 'group_name', + ), + mock.Mock(), + None, + ) + self.ext2 = extension.Extension( + 'name', + importlib_metadata.EntryPoint( + 'name', 'module:attribute', 'group_name', + ), + mock.Mock(), + None, + ) + + def test_module_name(self): + self.assertEqual('module.name', self.ext1.module_name) + self.assertEqual('module', self.ext2.module_name) + + def test_extras(self): + self.assertEqual(['[extra]'], self.ext1.extras) + self.assertEqual([], self.ext2.extras) + + def test_attr(self): + self.assertEqual('attribute.name', self.ext1.attr) + self.assertEqual('attribute', self.ext2.attr) + + def test_entry_point_target(self): + self.assertEqual('module.name:attribute.name [extra]', + self.ext1.entry_point_target) + self.assertEqual('module:attribute', + self.ext2.entry_point_target) diff --git a/libs/common/stevedore/tests/test_named.py b/libs/common/stevedore/tests/test_named.py index 757d0aab..d41dc2e5 100644 --- a/libs/common/stevedore/tests/test_named.py +++ b/libs/common/stevedore/tests/test_named.py @@ -10,11 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + from stevedore import named from stevedore.tests import utils -import mock - class TestNamed(utils.TestCase): def test_named(self): diff --git a/libs/common/stevedore/tests/test_sphinxext.py b/libs/common/stevedore/tests/test_sphinxext.py index 60b47944..e90bd679 100644 --- a/libs/common/stevedore/tests/test_sphinxext.py +++ b/libs/common/stevedore/tests/test_sphinxext.py @@ -12,25 +12,26 @@ """Tests for the sphinx extension """ -from __future__ import unicode_literals +try: + # For python 3.8 and later + import importlib.metadata as importlib_metadata +except ImportError: + # For everyone else + import importlib_metadata from stevedore import extension from stevedore import sphinxext from stevedore.tests import utils -import mock -import pkg_resources - def _make_ext(name, docstring): def inner(): pass inner.__doc__ = docstring - m1 = mock.Mock(spec=pkg_resources.EntryPoint) - m1.module_name = '%s_module' % name - s = mock.Mock(return_value='ENTRY_POINT(%s)' % name) - m1.__str__ = s + m1 = importlib_metadata.EntryPoint( + name, '{}_module:{}'.format(name, name), 'group', + ) return extension.Extension(name, m1, inner, None) @@ -112,7 +113,8 @@ class TestSphinxExt(utils.TestCase): ('nodoc', 'nodoc_module'), ('-----', 'nodoc_module'), ('\n', 'nodoc_module'), - ('.. warning:: No documentation found in ENTRY_POINT(nodoc)', + (('.. warning:: No documentation found for ' + 'nodoc in nodoc_module:nodoc'), 'nodoc_module'), ('\n', 'nodoc_module'), ], diff --git a/libs/common/stevedore/tests/test_test_manager.py b/libs/common/stevedore/tests/test_test_manager.py index df056cfe..3ada1397 100644 --- a/libs/common/stevedore/tests/test_test_manager.py +++ b/libs/common/stevedore/tests/test_test_manager.py @@ -10,14 +10,20 @@ # License for the specific language governing permissions and limitations # under the License. -from mock import Mock, sentinel -from stevedore import (ExtensionManager, NamedExtensionManager, HookManager, - DriverManager, EnabledExtensionManager) -from stevedore.dispatch import (DispatchExtensionManager, - NameDispatchExtensionManager) +from unittest.mock import Mock +from unittest.mock import sentinel + +from stevedore.dispatch import DispatchExtensionManager +from stevedore.dispatch import NameDispatchExtensionManager from stevedore.extension import Extension from stevedore.tests import utils +from stevedore import DriverManager +from stevedore import EnabledExtensionManager +from stevedore import ExtensionManager +from stevedore import HookManager +from stevedore import NamedExtensionManager + test_extension = Extension('test_extension', None, None, None) test_extension2 = Extension('another_one', None, None, None) diff --git a/libs/common/subliminal/__init__.py b/libs/common/subliminal/__init__.py index 7ff8ac34..91c91aec 100644 --- a/libs/common/subliminal/__init__.py +++ b/libs/common/subliminal/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- __title__ = 'subliminal' -__version__ = '2.0.5' +__version__ = '2.1.0' __short_version__ = '.'.join(__version__.split('.')[:2]) __author__ = 'Antoine Bertin' __license__ = 'MIT' diff --git a/libs/common/subliminal/cache.py b/libs/common/subliminal/cache.py index 244ba953..017c73e5 100644 --- a/libs/common/subliminal/cache.py +++ b/libs/common/subliminal/cache.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- import datetime +import six from dogpile.cache import make_region +from dogpile.cache.util import function_key_generator #: Expiration time for show caching SHOW_EXPIRATION_TIME = datetime.timedelta(weeks=3).total_seconds() @@ -13,4 +15,23 @@ EPISODE_EXPIRATION_TIME = datetime.timedelta(days=3).total_seconds() REFINER_EXPIRATION_TIME = datetime.timedelta(weeks=1).total_seconds() -region = make_region() +def _to_native_str(value): + if six.PY2: + # In Python 2, the native string type is bytes + if isinstance(value, six.text_type): # unicode for Python 2 + return value.encode('utf-8') + else: + return six.binary_type(value) + else: + # In Python 3, the native string type is unicode + if isinstance(value, six.binary_type): # bytes for Python 3 + return value.decode('utf-8') + else: + return six.text_type(value) + + +def to_native_str_key_generator(namespace, fn, to_str=_to_native_str): + return function_key_generator(namespace, fn, to_str) + + +region = make_region(function_key_generator=to_native_str_key_generator) diff --git a/libs/common/subliminal/cli.py b/libs/common/subliminal/cli.py index cc24853c..76d7de2e 100644 --- a/libs/common/subliminal/cli.py +++ b/libs/common/subliminal/cli.py @@ -163,6 +163,26 @@ class Config(object): for k, v in config.items(): self.config.set(provider, k, v) + @property + def refiner_configs(self): + rv = {} + for refiner in refiner_manager: + if self.config.has_section(refiner.name): + rv[refiner.name] = {k: v for k, v in self.config.items(refiner.name)} + return rv + + @refiner_configs.setter + def refiner_configs(self, value): + # loop over refiner configurations + for refiner, config in value.items(): + # create the corresponding section if necessary + if not self.config.has_section(refiner): + self.config.add_section(refiner) + + # add config options + for k, v in config.items(): + self.config.set(refiner, k, v) + class LanguageParamType(click.ParamType): """:class:`~click.ParamType` for languages that returns a :class:`~babelfish.language.Language`""" @@ -174,6 +194,7 @@ class LanguageParamType(click.ParamType): except BabelfishError: self.fail('%s is not a valid language' % value) + LANGUAGE = LanguageParamType() @@ -202,6 +223,7 @@ class AgeParamType(click.ParamType): return timedelta(**{k: int(v) for k, v in match.groupdict(0).items()}) + AGE = AgeParamType() PROVIDER = click.Choice(sorted(provider_manager.names())) @@ -219,13 +241,13 @@ config_file = 'config.ini' @click.option('--legendastv', type=click.STRING, nargs=2, metavar='USERNAME PASSWORD', help='LegendasTV configuration.') @click.option('--opensubtitles', type=click.STRING, nargs=2, metavar='USERNAME PASSWORD', help='OpenSubtitles configuration.') -@click.option('--subscenter', type=click.STRING, nargs=2, metavar='USERNAME PASSWORD', help='SubsCenter configuration.') +@click.option('--omdb', type=click.STRING, nargs=1, metavar='APIKEY', help='OMDB API key.') @click.option('--cache-dir', type=click.Path(writable=True, file_okay=False), default=dirs.user_cache_dir, show_default=True, expose_value=True, help='Path to the cache directory.') @click.option('--debug', is_flag=True, help='Print useful information for debugging subliminal and for reporting bugs.') @click.version_option(__version__) @click.pass_context -def subliminal(ctx, addic7ed, legendastv, opensubtitles, subscenter, cache_dir, debug): +def subliminal(ctx, addic7ed, legendastv, opensubtitles, omdb, cache_dir, debug): """Subtitles, faster than your thoughts.""" # create cache directory try: @@ -245,16 +267,23 @@ def subliminal(ctx, addic7ed, legendastv, opensubtitles, subscenter, cache_dir, logging.getLogger('subliminal').addHandler(handler) logging.getLogger('subliminal').setLevel(logging.DEBUG) + ctx.obj = { + 'provider_configs': {}, + 'refiner_configs': {} + } + # provider configs - ctx.obj = {'provider_configs': {}} if addic7ed: ctx.obj['provider_configs']['addic7ed'] = {'username': addic7ed[0], 'password': addic7ed[1]} if legendastv: ctx.obj['provider_configs']['legendastv'] = {'username': legendastv[0], 'password': legendastv[1]} if opensubtitles: ctx.obj['provider_configs']['opensubtitles'] = {'username': opensubtitles[0], 'password': opensubtitles[1]} - if subscenter: - ctx.obj['provider_configs']['subscenter'] = {'username': subscenter[0], 'password': subscenter[1]} + ctx.obj['provider_configs']['opensubtitlesvip'] = {'username': opensubtitles[0], 'password': opensubtitles[1]} + + # refiner configs + if omdb: + ctx.obj['refiner_configs']['omdb'] = {'apikey': omdb} @subliminal.command() @@ -324,8 +353,12 @@ def download(obj, provider, refiner, language, age, directory, encoding, single, continue if not force: video.subtitle_languages |= set(search_external_subtitles(video.name, directory=directory).values()) - refine(video, episode_refiners=refiner, movie_refiners=refiner, embedded_subtitles=not force) - videos.append(video) + + if check_video(video, languages=language, age=age, undefined=single): + refine(video, episode_refiners=refiner, movie_refiners=refiner, + refiner_configs=obj['refiner_configs'], + embedded_subtitles=not force, providers=provider, languages=language) + videos.append(video) continue # directories @@ -341,7 +374,9 @@ def download(obj, provider, refiner, language, age, directory, encoding, single, video.subtitle_languages |= set(search_external_subtitles(video.name, directory=directory).values()) if check_video(video, languages=language, age=age, undefined=single): - refine(video, episode_refiners=refiner, movie_refiners=refiner, embedded_subtitles=not force) + refine(video, episode_refiners=refiner, movie_refiners=refiner, + refiner_configs=obj['refiner_configs'], embedded_subtitles=not force, + providers=provider, languages=language) videos.append(video) else: ignored_videos.append(video) @@ -357,7 +392,9 @@ def download(obj, provider, refiner, language, age, directory, encoding, single, if not force: video.subtitle_languages |= set(search_external_subtitles(video.name, directory=directory).values()) if check_video(video, languages=language, age=age, undefined=single): - refine(video, episode_refiners=refiner, movie_refiners=refiner, embedded_subtitles=not force) + refine(video, episode_refiners=refiner, movie_refiners=refiner, + refiner_configs=obj['refiner_configs'], embedded_subtitles=not force, + providers=provider, languages=language) videos.append(video) else: ignored_videos.append(video) diff --git a/libs/common/subliminal/core.py b/libs/common/subliminal/core.py index c516c49d..b6af6d92 100644 --- a/libs/common/subliminal/core.py +++ b/libs/common/subliminal/core.py @@ -6,18 +6,17 @@ import io import itertools import logging import operator -import os.path -import socket +import os from babelfish import Language, LanguageReverseError from guessit import guessit -from rarfile import NotRarFile, RarCannotExec, RarFile -import requests +from rarfile import BadRarFile, NotRarFile, RarCannotExec, RarFile, Error, is_rarfile +from zipfile import BadZipfile -from .extensions import provider_manager, refiner_manager +from .extensions import provider_manager, default_providers, refiner_manager from .score import compute_score as default_compute_score -from .subtitle import SUBTITLE_EXTENSIONS, get_subtitle_path -from .utils import hash_napiprojekt, hash_opensubtitles, hash_shooter, hash_thesubdb +from .subtitle import SUBTITLE_EXTENSIONS +from .utils import handle_exception from .video import VIDEO_EXTENSIONS, Episode, Movie, Video #: Supported archive extensions @@ -37,12 +36,12 @@ class ProviderPool(object): :param list providers: name of providers to use, if not all. :param dict provider_configs: provider configuration as keyword arguments per provider name to pass when - instanciating the :class:`~subliminal.providers.Provider`. + instantiating the :class:`~subliminal.providers.Provider`. """ def __init__(self, providers=None, provider_configs=None): #: Name of providers to use - self.providers = providers or provider_manager.names() + self.providers = providers or default_providers #: Provider configuration self.provider_configs = provider_configs or {} @@ -77,10 +76,8 @@ class ProviderPool(object): try: logger.info('Terminating provider %s', name) self.initialized_providers[name].terminate() - except (requests.Timeout, socket.timeout): - logger.error('Provider %r timed out, improperly terminated', name) - except: - logger.exception('Provider %r terminated unexpectedly', name) + except Exception as e: + handle_exception(e, 'Provider {} improperly terminated'.format(name)) del self.initialized_providers[name] @@ -107,7 +104,7 @@ class ProviderPool(object): return [] # check supported languages - provider_languages = provider_manager[provider].plugin.languages & languages + provider_languages = provider_manager[provider].plugin.check_languages(languages) if not provider_languages: logger.info('Skipping provider %r: no language to search for', provider) return [] @@ -116,10 +113,8 @@ class ProviderPool(object): logger.info('Listing subtitles with provider %r and languages %r', provider, provider_languages) try: return self[provider].list_subtitles(video, provider_languages) - except (requests.Timeout, socket.timeout): - logger.error('Provider %r timed out', provider) - except: - logger.exception('Unexpected error in provider %r', provider) + except Exception as e: + handle_exception(e, 'Provider {}'.format(provider)) def list_subtitles(self, video, languages): """List subtitles. @@ -169,14 +164,11 @@ class ProviderPool(object): logger.info('Downloading subtitle %r', subtitle) try: self[subtitle.provider_name].download_subtitle(subtitle) - except (requests.Timeout, socket.timeout): - logger.error('Provider %r timed out, discarding it', subtitle.provider_name) + except (BadZipfile, BadRarFile): + logger.error('Bad archive for subtitle %r', subtitle) + except Exception as e: + handle_exception(e, 'Discarding provider {}'.format(subtitle.provider_name)) self.discarded_providers.add(subtitle.provider_name) - return False - except: - logger.exception('Unexpected error in provider %r, discarding it', subtitle.provider_name) - self.discarded_providers.add(subtitle.provider_name) - return False # check subtitle validity if not subtitle.is_valid(): @@ -338,7 +330,7 @@ def search_external_subtitles(path, directory=None): subtitles = {} for p in os.listdir(directory or dirpath): # keep only valid subtitle filenames - if not p.startswith(fileroot) or not p.endswith(SUBTITLE_EXTENSIONS): + if not p.startswith(fileroot) or not p.lower().endswith(SUBTITLE_EXTENSIONS): continue # extract the potential language code @@ -370,7 +362,7 @@ def scan_video(path): raise ValueError('Path does not exist') # check video extension - if not path.endswith(VIDEO_EXTENSIONS): + if not path.lower().endswith(VIDEO_EXTENSIONS): raise ValueError('%r is not a valid video extension' % os.path.splitext(path)[1]) dirpath, filename = os.path.split(path) @@ -379,17 +371,9 @@ def scan_video(path): # guess video = Video.fromguess(path, guessit(path)) - # size and hashes + # size video.size = os.path.getsize(path) - if video.size > 10485760: - logger.debug('Size is %d', video.size) - video.hashes['opensubtitles'] = hash_opensubtitles(path) - video.hashes['shooter'] = hash_shooter(path) - video.hashes['thesubdb'] = hash_thesubdb(path) - video.hashes['napiprojekt'] = hash_napiprojekt(path) - logger.debug('Computed hashes %r', video.hashes) - else: - logger.warning('Size is lower than 10MB: hashes not computed') + logger.debug('Size is %d', video.size) return video @@ -406,37 +390,43 @@ def scan_archive(path): if not os.path.exists(path): raise ValueError('Path does not exist') - # check video extension - if not path.endswith(ARCHIVE_EXTENSIONS): - raise ValueError('%r is not a valid archive extension' % os.path.splitext(path)[1]) + if not is_rarfile(path): + raise ValueError("'{0}' is not a valid archive".format(os.path.splitext(path)[1])) - dirpath, filename = os.path.split(path) - logger.info('Scanning archive %r in %r', filename, dirpath) + dir_path, filename = os.path.split(path) - # rar extension - if filename.endswith('.rar'): - rar = RarFile(path) + logger.info('Scanning archive %r in %r', filename, dir_path) - # filter on video extensions - rar_filenames = [f for f in rar.namelist() if f.endswith(VIDEO_EXTENSIONS)] + # Get filename and file size from RAR + rar = RarFile(path) - # no video found - if not rar_filenames: - raise ValueError('No video in archive') + # check that the rar doesnt need a password + if rar.needs_password(): + raise ValueError('Rar requires a password') - # more than one video found - if len(rar_filenames) > 1: - raise ValueError('More than one video in archive') + # raise an exception if the rar file is broken + # must be called to avoid a potential deadlock with some broken rars + rar.testrar() - # guess - rar_filename = rar_filenames[0] - rar_filepath = os.path.join(dirpath, rar_filename) - video = Video.fromguess(rar_filepath, guessit(rar_filepath)) + file_info = [f for f in rar.infolist() if not f.isdir() and f.filename.endswith(VIDEO_EXTENSIONS)] - # size - video.size = rar.getinfo(rar_filename).file_size - else: - raise ValueError('Unsupported extension %r' % os.path.splitext(path)[1]) + # sort by file size descending, the largest video in the archive is the one we want, there may be samples or intros + file_info.sort(key=operator.attrgetter('file_size'), reverse=True) + + # no video found + if not file_info: + raise ValueError('No video in archive') + + # Free the information about irrelevant files before guessing + file_info = file_info[0] + + # guess + video_filename = file_info.filename + video_path = os.path.join(dir_path, video_filename) + video = Video.fromguess(video_path, guessit(video_path)) + + # size + video.size = file_info.file_size return video @@ -471,17 +461,26 @@ def scan_videos(path, age=None, archives=True): if dirname.startswith('.'): logger.debug('Skipping hidden dirname %r in %r', dirname, dirpath) dirnames.remove(dirname) + # Skip Sample folder + if dirname.lower() == 'sample': + logger.debug('Skipping sample dirname %r in %r', dirname, dirpath) + dirnames.remove(dirname) # scan for videos for filename in filenames: # filter on videos and archives - if not (filename.endswith(VIDEO_EXTENSIONS) or archives and filename.endswith(ARCHIVE_EXTENSIONS)): + if not (filename.lower().endswith(VIDEO_EXTENSIONS) or + archives and filename.lower().endswith(ARCHIVE_EXTENSIONS)): continue # skip hidden files if filename.startswith('.'): logger.debug('Skipping hidden filename %r in %r', filename, dirpath) continue + # skip 'sample' media files + if os.path.splitext(filename)[0].lower() == 'sample': + logger.debug('Skipping sample filename %r in %r', filename, dirpath) + continue # reconstruct the file path filepath = os.path.join(dirpath, filename) @@ -492,21 +491,27 @@ def scan_videos(path, age=None, archives=True): continue # skip old files - if age and datetime.utcnow() - datetime.utcfromtimestamp(os.path.getmtime(filepath)) > age: - logger.debug('Skipping old file %r in %r', filename, dirpath) + try: + file_age = datetime.utcfromtimestamp(os.path.getmtime(filepath)) + except ValueError: + logger.warning('Could not get age of file %r in %r', filename, dirpath) continue + else: + if age and datetime.utcnow() - file_age > age: + logger.debug('Skipping old file %r in %r', filename, dirpath) + continue # scan - if filename.endswith(VIDEO_EXTENSIONS): # video + if filename.lower().endswith(VIDEO_EXTENSIONS): # video try: video = scan_video(filepath) except ValueError: # pragma: no cover logger.exception('Error scanning video') continue - elif archives and filename.endswith(ARCHIVE_EXTENSIONS): # archive + elif archives and filename.lower().endswith(ARCHIVE_EXTENSIONS): # archive try: video = scan_archive(filepath) - except (NotRarFile, RarCannotExec, ValueError): # pragma: no cover + except (Error, NotRarFile, RarCannotExec, ValueError): # pragma: no cover logger.exception('Error scanning archive') continue else: # pragma: no cover @@ -517,7 +522,7 @@ def scan_videos(path, age=None, archives=True): return videos -def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): +def refine(video, episode_refiners=None, movie_refiners=None, refiner_configs=None, **kwargs): """Refine a video using :ref:`refiners`. .. note:: @@ -528,6 +533,8 @@ def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): :type video: :class:`~subliminal.video.Video` :param tuple episode_refiners: refiners to use for episodes. :param tuple movie_refiners: refiners to use for movies. + :param dict refiner_configs: refiner configuration as keyword arguments per refiner name to pass when + calling the refine method :param \*\*kwargs: additional parameters for the :func:`~subliminal.refiners.refine` functions. """ @@ -536,12 +543,12 @@ def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): refiners = episode_refiners or ('metadata', 'tvdb', 'omdb') elif isinstance(video, Movie): refiners = movie_refiners or ('metadata', 'omdb') - for refiner in refiners: + for refiner in ('hash', ) + refiners: logger.info('Refining video with %s', refiner) try: - refiner_manager[refiner].plugin(video, **kwargs) - except: - logger.exception('Failed to refine video') + refiner_manager[refiner].plugin(video, **dict((refiner_configs or {}).get(refiner, {}), **kwargs)) + except Exception as e: + handle_exception(e, 'Failed to refine video {0!r}'.format(video.name)) def list_subtitles(videos, languages, pool_class=ProviderPool, **kwargs): @@ -684,7 +691,7 @@ def save_subtitles(video, subtitles, single=False, directory=None, encoding=None continue # create subtitle path - subtitle_path = get_subtitle_path(video.name, None if single else subtitle.language) + subtitle_path = subtitle.get_path(video, single=single) if directory is not None: subtitle_path = os.path.join(directory, os.path.split(subtitle_path)[1]) diff --git a/libs/common/subliminal/exceptions.py b/libs/common/subliminal/exceptions.py index 5f5c7a77..14d4f641 100644 --- a/libs/common/subliminal/exceptions.py +++ b/libs/common/subliminal/exceptions.py @@ -19,8 +19,8 @@ class AuthenticationError(ProviderError): pass -class TooManyRequests(ProviderError): - """Exception raised by providers when too many requests are made.""" +class ServiceUnavailable(ProviderError): + """Exception raised when status is '503 Service Unavailable'.""" pass diff --git a/libs/common/subliminal/extensions.py b/libs/common/subliminal/extensions.py index 1f378b7f..7ac277bc 100644 --- a/libs/common/subliminal/extensions.py +++ b/libs/common/subliminal/extensions.py @@ -29,9 +29,9 @@ class RegistrableExtensionManager(ExtensionManager): super(RegistrableExtensionManager, self).__init__(namespace, **kwargs) - def _find_entry_points(self, namespace): + def list_entry_points(self): # copy of default extensions - eps = list(super(RegistrableExtensionManager, self)._find_entry_points(namespace)) + eps = list(super(RegistrableExtensionManager, self).list_entry_points()) # internal extensions for iep in self.internal_extensions: @@ -89,17 +89,25 @@ class RegistrableExtensionManager(ExtensionManager): #: Provider manager provider_manager = RegistrableExtensionManager('subliminal.providers', [ 'addic7ed = subliminal.providers.addic7ed:Addic7edProvider', + 'argenteam = subliminal.providers.argenteam:ArgenteamProvider', 'legendastv = subliminal.providers.legendastv:LegendasTVProvider', 'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider', + 'opensubtitlesvip = subliminal.providers.opensubtitles:OpenSubtitlesVipProvider', 'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider', 'shooter = subliminal.providers.shooter:ShooterProvider', - 'subscenter = subliminal.providers.subscenter:SubsCenterProvider', 'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider', 'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider' ]) +#: Disabled providers +disabled_providers = ['opensubtitlesvip'] + +#: Default enabled providers +default_providers = [p for p in provider_manager.names() if p not in disabled_providers] + #: Refiner manager refiner_manager = RegistrableExtensionManager('subliminal.refiners', [ + 'hash = subliminal.refiners.hash:refine', 'metadata = subliminal.refiners.metadata:refine', 'omdb = subliminal.refiners.omdb:refine', 'tvdb = subliminal.refiners.tvdb:refine' diff --git a/libs/common/subliminal/matches.py b/libs/common/subliminal/matches.py new file mode 100644 index 00000000..9b230902 --- /dev/null +++ b/libs/common/subliminal/matches.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- +from rebulk.loose import ensure_list + +from .score import get_equivalent_release_groups, score_keys +from .video import Episode, Movie +from .utils import sanitize, sanitize_release_group + + +def series_matches(video, title=None, **kwargs): + """Whether the `video` matches the series title. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str title: the series name. + :return: whether there's a match + :rtype: bool + + """ + if isinstance(video, Episode): + return video.series and sanitize(title) in ( + sanitize(name) for name in [video.series] + video.alternative_series + ) + + +def title_matches(video, title=None, episode_title=None, **kwargs): + """Whether the movie matches the movie `title` or the series matches the `episode_title`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str title: the movie title. + :param str episode_title: the series episode title. + :return: whether there's a match + :rtype: bool + + """ + if isinstance(video, Episode): + return video.title and sanitize(episode_title) == sanitize(video.title) + if isinstance(video, Movie): + return video.title and sanitize(title) == sanitize(video.title) + + +def season_matches(video, season=None, **kwargs): + """Whether the episode matches the `season`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param int season: the episode season. + :return: whether there's a match + :rtype: bool + + """ + if isinstance(video, Episode): + return video.season and season == video.season + + +def episode_matches(video, episode=None, **kwargs): + """Whether the episode matches the `episode`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param episode: the episode season. + :type: list of int or int + :return: whether there's a match + :rtype: bool + + """ + if isinstance(video, Episode): + return video.episodes and ensure_list(episode) == video.episodes + + +def year_matches(video, year=None, partial=False, **kwargs): + """Whether the video matches the `year`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param int year: the video year. + :param bool partial: whether or not the guess is partial. + :return: whether there's a match + :rtype: bool + + """ + if video.year and year == video.year: + return True + if isinstance(video, Episode): + # count "no year" as an information + return not partial and video.original_series and not year + + +def country_matches(video, country=None, partial=False, **kwargs): + """Whether the video matches the `country`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param country: the video country. + :type country: :class:`~babelfish.country.Country` + :param bool partial: whether or not the guess is partial. + :return: whether there's a match + :rtype: bool + + """ + if video.country and country == video.country: + return True + + if isinstance(video, Episode): + # count "no country" as an information + return not partial and video.original_series and not country + + if isinstance(video, Movie): + # count "no country" as an information + return not video.country and not country + + +def release_group_matches(video, release_group=None, **kwargs): + """Whether the video matches the `release_group`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str release_group: the video release group. + :return: whether there's a match + :rtype: bool + + """ + return (video.release_group and release_group and + any(r in sanitize_release_group(release_group) + for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))) + + +def streaming_service_matches(video, streaming_service=None, **kwargs): + """Whether the video matches the `streaming_service`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str streaming_service: the video streaming service + :return: whether there's a match + :rtype: bool + + """ + return video.streaming_service and streaming_service == video.streaming_service + + +def resolution_matches(video, screen_size=None, **kwargs): + """Whether the video matches the `resolution`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str screen_size: the video resolution + :return: whether there's a match + :rtype: bool + + """ + return video.resolution and screen_size == video.resolution + + +def source_matches(video, source=None, **kwargs): + """Whether the video matches the `source`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str source: the video source + :return: whether there's a match + :rtype: bool + + """ + return video.source and source == video.source + + +def video_codec_matches(video, video_codec=None, **kwargs): + """Whether the video matches the `video_codec`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str video_codec: the video codec + :return: whether there's a match + :rtype: bool + + """ + return video.video_codec and video_codec == video.video_codec + + +def audio_codec_matches(video, audio_codec=None, **kwargs): + """Whether the video matches the `audio_codec`. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param str audio_codec: the video audio codec + :return: whether there's a match + :rtype: bool + + """ + return video.audio_codec and audio_codec == video.audio_codec + + +#: Available matches functions +matches_manager = { + 'series': series_matches, + 'title': title_matches, + 'season': season_matches, + 'episode': episode_matches, + 'year': year_matches, + 'country': country_matches, + 'release_group': release_group_matches, + 'streaming_service': streaming_service_matches, + 'resolution': resolution_matches, + 'source': source_matches, + 'video_codec': video_codec_matches, + 'audio_codec': audio_codec_matches +} + + +def guess_matches(video, guess, partial=False): + """Get matches between a `video` and a `guess`. + + If a guess is `partial`, the absence information won't be counted as a match. + + :param video: the video. + :type video: :class:`~subliminal.video.Video` + :param guess: the guess. + :type guess: dict + :param bool partial: whether or not the guess is partial. + :return: matches between the `video` and the `guess`. + :rtype: set + + """ + matches = set() + for key in score_keys: + if key in matches_manager and matches_manager[key](video, partial=partial, **guess): + matches.add(key) + + return matches diff --git a/libs/common/subliminal/providers/__init__.py b/libs/common/subliminal/providers/__init__.py index 9d2fd6d2..fd102cdc 100644 --- a/libs/common/subliminal/providers/__init__.py +++ b/libs/common/subliminal/providers/__init__.py @@ -4,6 +4,7 @@ import logging from bs4 import BeautifulSoup, FeatureNotFound from six.moves.xmlrpc_client import SafeTransport +from .. import __short_version__ from ..video import Episode, Movie logger = logging.getLogger(__name__) @@ -68,6 +69,12 @@ class Provider(object): #: Required hash, if any required_hash = None + #: Subtitle class to use + subtitle_class = None + + #: User Agent to use + user_agent = 'Subliminal/%s' % __short_version__ + def __enter__(self): self.initialize() return self @@ -111,13 +118,41 @@ class Provider(object): :rtype: bool """ - if not isinstance(video, cls.video_types): + if not cls.check_types(video): return False if cls.required_hash is not None and cls.required_hash not in video.hashes: return False return True + @classmethod + def check_types(cls, video): + """Check if the `video` type is supported by the provider. + + The `video` is considered invalid if not an instance of :attr:`video_types`. + + :param video: the video to check. + :type video: :class:`~subliminal.video.Video` + :return: `True` if the `video` is valid, `False` otherwise. + :rtype: bool + + """ + return isinstance(video, cls.video_types) + + @classmethod + def check_languages(cls, languages): + """Check if the `languages` are supported by the provider. + + A subset of the supported languages is returned. + + :param languages: the languages to check. + :type languages: set of :class:`~babelfish.language.Language` + :return: subset of the supported languages. + :rtype: set of :class:`~babelfish.language.Language` + + """ + return cls.languages & languages + def query(self, *args, **kwargs): """Query the provider for subtitles. diff --git a/libs/common/subliminal/providers/addic7ed.py b/libs/common/subliminal/providers/addic7ed.py index 0d4a58fd..6b5802e9 100644 --- a/libs/common/subliminal/providers/addic7ed.py +++ b/libs/common/subliminal/providers/addic7ed.py @@ -7,20 +7,22 @@ from guessit import guessit from requests import Session from . import ParserBeautifulSoup, Provider -from .. import __short_version__ from ..cache import SHOW_EXPIRATION_TIME, region -from ..exceptions import AuthenticationError, ConfigurationError, DownloadLimitExceeded, TooManyRequests -from ..score import get_equivalent_release_groups -from ..subtitle import Subtitle, fix_line_ending, guess_matches -from ..utils import sanitize, sanitize_release_group +from ..exceptions import AuthenticationError, ConfigurationError, DownloadLimitExceeded +from ..matches import guess_matches +from ..subtitle import Subtitle, fix_line_ending +from ..utils import sanitize from ..video import Episode logger = logging.getLogger(__name__) language_converters.register('addic7ed = subliminal.converters.addic7ed:Addic7edConverter') +# Series cell matching regex +show_cells_re = re.compile(b'.*?', re.DOTALL) + #: Series header parsing regex -series_year_re = re.compile(r'^(?P[ \w\'.:(),&!?-]+?)(?: \((?P\d{4})\))?$') +series_year_re = re.compile(r'^(?P[ \w\'.:(),*&!?-]+?)(?: \((?P\d{4})\))?$') class Addic7edSubtitle(Subtitle): @@ -29,7 +31,7 @@ class Addic7edSubtitle(Subtitle): def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, year, version, download_link): - super(Addic7edSubtitle, self).__init__(language, hearing_impaired, page_link) + super(Addic7edSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link) self.series = series self.season = season self.episode = episode @@ -42,37 +44,31 @@ class Addic7edSubtitle(Subtitle): def id(self): return self.download_link - def get_matches(self, video): - matches = set() + @property + def info(self): + return '{series}{yopen}{year}{yclose} s{season:02d}e{episode:02d}{topen}{title}{tclose}{version}'.format( + series=self.series, season=self.season, episode=self.episode, title=self.title, year=self.year or '', + version=self.version, yopen=' (' if self.year else '', yclose=')' if self.year else '', + topen=' - ' if self.title else '', tclose=' - ' if self.version else '' + ) + + def get_matches(self, video): + # series name + matches = guess_matches(video, { + 'title': self.series, + 'season': self.season, + 'episode': self.episode, + 'episode_title': self.title, + 'year': self.year, + 'release_group': self.version, + }) - # series - if video.series and sanitize(self.series) == sanitize(video.series): - matches.add('series') - # season - if video.season and self.season == video.season: - matches.add('season') - # episode - if video.episode and self.episode == video.episode: - matches.add('episode') - # title - if video.title and sanitize(self.title) == sanitize(video.title): - matches.add('title') - # year - if video.original_series and self.year is None or video.year and video.year == self.year: - matches.add('year') - # release_group - if (video.release_group and self.version and - any(r in sanitize_release_group(self.version) - for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))): - matches.add('release_group') # resolution if video.resolution and self.version and video.resolution in self.version.lower(): matches.add('resolution') - # format - if video.format and self.version and video.format.lower() in self.version.lower(): - matches.add('format') # other properties - matches |= guess_matches(video, guessit(self.version), partial=True) + if self.version: + matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True) return matches @@ -86,21 +82,23 @@ class Addic7edProvider(Provider): ]} video_types = (Episode,) server_url = 'http://www.addic7ed.com/' + subtitle_class = Addic7edSubtitle def __init__(self, username=None, password=None): - if username is not None and password is None or username is None and password is not None: + if any((username, password)) and not all((username, password)): raise ConfigurationError('Username and password must be specified') self.username = username self.password = password self.logged_in = False + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent # login - if self.username is not None and self.password is not None: + if self.username and self.password: logger.info('Logging in') data = {'username': self.username, 'password': self.password, 'Submit': 'Log in'} r = self.session.post(self.server_url + 'dologin.php', data, allow_redirects=False, timeout=10) @@ -134,7 +132,16 @@ class Addic7edProvider(Provider): logger.info('Getting show ids') r = self.session.get(self.server_url + 'shows.php', timeout=10) r.raise_for_status() - soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) + + # LXML parser seems to fail when parsing Addic7ed.com HTML markup. + # Last known version to work properly is 3.6.4 (next version, 3.7.0, fails) + # Assuming the site's markup is bad, and stripping it down to only contain what's needed. + show_cells = re.findall(show_cells_re, r.content) + if show_cells: + soup = ParserBeautifulSoup(b''.join(show_cells), ['lxml', 'html.parser']) + else: + # If RegEx fails, fall back to original r.content and use 'html.parser' + soup = ParserBeautifulSoup(r.content, ['html.parser']) # populate the show ids show_ids = {} @@ -164,10 +171,8 @@ class Addic7edProvider(Provider): # make the search logger.info('Searching show ids with %r', params) - r = self.session.get(self.server_url + 'search.php', params=params, timeout=10) + r = self.session.get(self.server_url + 'srch.php', params=params, timeout=10) r.raise_for_status() - if r.status_code == 304: - raise TooManyRequests() soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) # get the suggestion @@ -218,24 +223,23 @@ class Addic7edProvider(Provider): # search as last resort if not show_id: - logger.warning('Series not found in show ids') + logger.warning('Series %s not found in show ids', series) show_id = self._search_show_id(series) return show_id - def query(self, series, season, year=None, country=None): - # get the show id - show_id = self.get_show_id(series, year, country) - if show_id is None: - logger.error('No show id found for %r (%r)', series, {'year': year, 'country': country}) - return [] - + def query(self, show_id, series, season, year=None, country=None): # get the page of the season of the show logger.info('Getting the page of show id %d, season %d', show_id, season) - r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=10) + r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=10) r.raise_for_status() - if r.status_code == 304: - raise TooManyRequests() + + if not r.content: + # Provider returns a status of 304 Not Modified with an empty content + # raise_for_status won't raise exception for that status code + logger.debug('No data returned from provider') + return [] + soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) # loop over subtitle rows @@ -262,16 +266,32 @@ class Addic7edProvider(Provider): version = cells[4].text download_link = cells[9].a['href'][1:] - subtitle = Addic7edSubtitle(language, hearing_impaired, page_link, series, season, episode, title, year, - version, download_link) + subtitle = self.subtitle_class(language, hearing_impaired, page_link, series, season, episode, title, year, + version, download_link) logger.debug('Found subtitle %r', subtitle) subtitles.append(subtitle) return subtitles def list_subtitles(self, video, languages): - return [s for s in self.query(video.series, video.season, video.year) - if s.language in languages and s.episode == video.episode] + # lookup show_id + titles = [video.series] + video.alternative_series + show_id = None + for title in titles: + show_id = self.get_show_id(title, video.year) + if show_id is not None: + break + + # query for subtitles with the show_id + if show_id is not None: + subtitles = [s for s in self.query(show_id, title, video.season, video.year) + if s.language in languages and s.episode == video.episode] + if subtitles: + return subtitles + else: + logger.error('No show id found for %r (%r)', video.series, {'year': video.year}) + + return [] def download_subtitle(self, subtitle): # download the subtitle @@ -280,6 +300,12 @@ class Addic7edProvider(Provider): timeout=10) r.raise_for_status() + if not r.content: + # Provider returns a status of 304 Not Modified with an empty content + # raise_for_status won't raise exception for that status code + logger.debug('Unable to download subtitle. No data returned from provider') + return + # detect download limit exceeded if r.headers['Content-Type'] == 'text/html': raise DownloadLimitExceeded diff --git a/libs/common/subliminal/providers/argenteam.py b/libs/common/subliminal/providers/argenteam.py new file mode 100644 index 00000000..59f3fc56 --- /dev/null +++ b/libs/common/subliminal/providers/argenteam.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +import io +import json +import logging +from zipfile import ZipFile + +from babelfish import Language +from guessit import guessit +from requests import Session +from six.moves import urllib + +from . import Provider +from ..cache import EPISODE_EXPIRATION_TIME, region +from ..exceptions import ProviderError +from ..matches import guess_matches +from ..subtitle import Subtitle, fix_line_ending +from ..video import Episode + +logger = logging.getLogger(__name__) + + +class ArgenteamSubtitle(Subtitle): + provider_name = 'argenteam' + + def __init__(self, language, download_link, series, season, episode, release, version): + super(ArgenteamSubtitle, self).__init__(language, download_link) + self.download_link = download_link + self.series = series + self.season = season + self.episode = episode + self.release = release + self.version = version + + @property + def id(self): + return self.download_link + + @property + def info(self): + return urllib.parse.unquote(self.download_link.rsplit('/')[-1]) + + def get_matches(self, video): + matches = guess_matches(video, { + 'title': self.series, + 'season': self.season, + 'episode': self.episode, + 'release_group': self.version + }) + + # resolution + if video.resolution and self.version and video.resolution in self.version.lower(): + matches.add('resolution') + + matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True) + return matches + + +class ArgenteamProvider(Provider): + provider_name = 'argenteam' + language = Language.fromalpha2('es') + languages = {language} + video_types = (Episode,) + server_url = "http://argenteam.net/api/v1/" + subtitle_class = ArgenteamSubtitle + + def __init__(self): + self.session = None + + def initialize(self): + self.session = Session() + self.session.headers['User-Agent'] = self.user_agent + + def terminate(self): + self.session.close() + + @region.cache_on_arguments(expiration_time=EPISODE_EXPIRATION_TIME, should_cache_fn=lambda value: value) + def search_episode_id(self, series, season, episode): + """Search the episode id from the `series`, `season` and `episode`. + + :param str series: series of the episode. + :param int season: season of the episode. + :param int episode: episode number. + :return: the episode id, if any. + :rtype: int or None + + """ + # make the search + query = '%s S%#02dE%#02d' % (series, season, episode) + logger.info('Searching episode id for %r', query) + r = self.session.get(self.server_url + 'search', params={'q': query}, timeout=10) + r.raise_for_status() + results = json.loads(r.text) + if results['total'] == 1: + return results['results'][0]['id'] + + logger.error('No episode id found for %r', series) + + def query(self, series, season, episode): + episode_id = self.search_episode_id(series, season, episode) + if episode_id is None: + return [] + + response = self.session.get(self.server_url + 'episode', params={'id': episode_id}, timeout=10) + response.raise_for_status() + content = json.loads(response.text) + subtitles = [] + for r in content['releases']: + for s in r['subtitles']: + subtitle = self.subtitle_class(self.language, s['uri'], series, season, episode, r['team'], r['tags']) + logger.debug('Found subtitle %r', subtitle) + subtitles.append(subtitle) + + return subtitles + + def list_subtitles(self, video, languages): + titles = [video.series] + video.alternative_series + for title in titles: + subs = self.query(title, video.season, video.episode) + if subs: + return subs + + return [] + + def download_subtitle(self, subtitle): + # download as a zip + logger.info('Downloading subtitle %r', subtitle) + r = self.session.get(subtitle.download_link, timeout=10) + r.raise_for_status() + + # open the zip + with ZipFile(io.BytesIO(r.content)) as zf: + if len(zf.namelist()) > 1: + raise ProviderError('More than one file to unzip') + + subtitle.content = fix_line_ending(zf.read(zf.namelist()[0])) diff --git a/libs/common/subliminal/providers/legendastv.py b/libs/common/subliminal/providers/legendastv.py index cdd16aca..9d696ca2 100644 --- a/libs/common/subliminal/providers/legendastv.py +++ b/libs/common/subliminal/providers/legendastv.py @@ -12,14 +12,16 @@ from guessit import guessit import pytz import rarfile from rarfile import RarFile, is_rarfile +from rebulk.loose import ensure_list from requests import Session from zipfile import ZipFile, is_zipfile from . import ParserBeautifulSoup, Provider -from .. import __short_version__ from ..cache import SHOW_EXPIRATION_TIME, region -from ..exceptions import AuthenticationError, ConfigurationError, ProviderError -from ..subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending, guess_matches, sanitize +from ..exceptions import AuthenticationError, ConfigurationError, ProviderError, ServiceUnavailable +from ..matches import guess_matches +from ..subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending +from ..utils import sanitize from ..video import Episode, Movie logger = logging.getLogger(__name__) @@ -44,8 +46,11 @@ rating_re = re.compile(r'nota (?P\d+)') #: Timestamp parsing regex timestamp_re = re.compile(r'(?P\d+)/(?P\d+)/(?P\d+) - (?P\d+):(?P\d+)') +#: Title with year/country regex +title_re = re.compile(r'^(?P.*?)(?: \((?:(?P\d{4})|(?P[A-Z]{2}))\))?$') + #: Cache key for releases -releases_key = __name__ + ':releases|{archive_id}' +releases_key = __name__ + ':releases|{archive_id}|{archive_name}' class LegendasTVArchive(object): @@ -60,8 +65,8 @@ class LegendasTVArchive(object): :param int rating: rating (0-10). :param timestamp: timestamp. :type timestamp: datetime.datetime - """ + def __init__(self, id, name, pack, featured, link, downloads=0, rating=0, timestamp=None): #: Identifier self.id = id @@ -96,10 +101,11 @@ class LegendasTVArchive(object): class LegendasTVSubtitle(Subtitle): """LegendasTV Subtitle.""" + provider_name = 'legendastv' def __init__(self, language, type, title, year, imdb_id, season, archive, name): - super(LegendasTVSubtitle, self).__init__(language, archive.link) + super(LegendasTVSubtitle, self).__init__(language, page_link=archive.link) self.type = type self.title = title self.year = year @@ -112,40 +118,28 @@ class LegendasTVSubtitle(Subtitle): def id(self): return '%s-%s' % (self.archive.id, self.name.lower()) + @property + def info(self): + return self.name + def get_matches(self, video, hearing_impaired=False): - matches = set() + matches = guess_matches(video, { + 'title': self.title, + 'year': self.year + }) # episode if isinstance(video, Episode) and self.type == 'episode': - # series - if video.series and sanitize(self.title) == sanitize(video.series): - matches.add('series') - - # year (year is based on season air date hence the adjustment) - if video.original_series and self.year is None or video.year and video.year == self.year - self.season + 1: - matches.add('year') - # imdb_id if video.series_imdb_id and self.imdb_id == video.series_imdb_id: matches.add('series_imdb_id') # movie elif isinstance(video, Movie) and self.type == 'movie': - # title - if video.title and sanitize(self.title) == sanitize(video.title): - matches.add('title') - - # year - if video.year and self.year == video.year: - matches.add('year') - # imdb_id if video.imdb_id and self.imdb_id == video.imdb_id: matches.add('imdb_id') - # archive name - matches |= guess_matches(video, guessit(self.archive.name, {'type': self.type})) - # name matches |= guess_matches(video, guessit(self.name, {'type': self.type})) @@ -157,29 +151,38 @@ class LegendasTVProvider(Provider): :param str username: username. :param str password: password. - """ + languages = {Language.fromlegendastv(l) for l in language_converters['legendastv'].codes} server_url = 'http://legendas.tv/' + subtitle_class = LegendasTVSubtitle def __init__(self, username=None, password=None): - if username and not password or not username and password: + + # Provider needs UNRAR installed. If not available raise ConfigurationError + try: + rarfile.custom_check([rarfile.UNRAR_TOOL], True) + except rarfile.RarExecError: + raise ConfigurationError('UNRAR tool not available') + + if any((username, password)) and not all((username, password)): raise ConfigurationError('Username and password must be specified') self.username = username self.password = password self.logged_in = False + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent # login - if self.username is not None and self.password is not None: + if self.username and self.password: logger.info('Logging in') data = {'_method': 'POST', 'data[User][username]': self.username, 'data[User][password]': self.password} r = self.session.post(self.server_url + 'login', data, allow_redirects=False, timeout=10) - r.raise_for_status() + raise_for_status(r) soup = ParserBeautifulSoup(r.content, ['html.parser']) if soup.find('div', {'class': 'alert-error'}, string=re.compile(u'Usuário ou senha inválidos')): @@ -193,94 +196,174 @@ class LegendasTVProvider(Provider): if self.logged_in: logger.info('Logging out') r = self.session.get(self.server_url + 'users/logout', allow_redirects=False, timeout=10) - r.raise_for_status() + raise_for_status(r) logger.debug('Logged out') self.logged_in = False self.session.close() - @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME) - def search_titles(self, title): + @staticmethod + def is_valid_title(title, title_id, sanitized_title, season, year): + """Check if is a valid title.""" + sanitized_result = sanitize(title['title']) + if sanitized_result != sanitized_title: + logger.debug("Mismatched title, discarding title %d (%s)", + title_id, sanitized_result) + return + + # episode type + if season: + # discard mismatches on type + if title['type'] != 'episode': + logger.debug("Mismatched 'episode' type, discarding title %d (%s)", title_id, sanitized_result) + return + + # discard mismatches on season + if 'season' not in title or title['season'] != season: + logger.debug('Mismatched season %s, discarding title %d (%s)', + title.get('season'), title_id, sanitized_result) + return + # movie type + else: + # discard mismatches on type + if title['type'] != 'movie': + logger.debug("Mismatched 'movie' type, discarding title %d (%s)", title_id, sanitized_result) + return + + # discard mismatches on year + if year is not None and 'year' in title and title['year'] != year: + logger.debug("Mismatched movie year, discarding title %d (%s)", title_id, sanitized_result) + return + return True + + @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME, should_cache_fn=lambda value: value) + def search_titles(self, title, season, title_year): """Search for titles matching the `title`. + For episodes, each season has it own title :param str title: the title to search for. + :param int season: season of the title + :param int title_year: year of the title :return: found titles. :rtype: dict - """ - # make the query - logger.info('Searching title %r', title) - r = self.session.get(self.server_url + 'legenda/sugestao/{}'.format(title), timeout=10) - r.raise_for_status() - results = json.loads(r.text) - - # loop over results titles = {} - for result in results: - source = result['_source'] + sanitized_titles = [sanitize(title)] + ignore_characters = {'\'', '.'} + if any(c in title for c in ignore_characters): + sanitized_titles.append(sanitize(title, ignore_characters=ignore_characters)) - # extract id - title_id = int(source['id_filme']) + for sanitized_title in sanitized_titles: + # make the query + if season: + logger.info('Searching episode title %r for season %r', sanitized_title, season) + else: + logger.info('Searching movie title %r', sanitized_title) - # extract type and title - title = {'type': type_map[source['tipo']], 'title': source['dsc_nome']} + r = self.session.get(self.server_url + 'legenda/sugestao/{}'.format(sanitized_title), timeout=10) + raise_for_status(r) + results = json.loads(r.text) - # extract year - if source['dsc_data_lancamento'] and source['dsc_data_lancamento'].isdigit(): - title['year'] = int(source['dsc_data_lancamento']) + # loop over results + for result in results: + source = result['_source'] - # extract imdb_id - if source['id_imdb'] != '0': - if not source['id_imdb'].startswith('tt'): - title['imdb_id'] = 'tt' + source['id_imdb'].zfill(7) - else: - title['imdb_id'] = source['id_imdb'] + # extract id + title_id = int(source['id_filme']) - # extract season - if title['type'] == 'episode': - if source['temporada'] and source['temporada'].isdigit(): - title['season'] = int(source['temporada']) - else: - match = season_re.search(source['dsc_nome_br']) - if match: - title['season'] = int(match.group('season')) + # extract type + title = {'type': type_map[source['tipo']]} + + # extract title, year and country + name, year, country = title_re.match(source['dsc_nome']).groups() + title['title'] = name + + # extract imdb_id + if source['id_imdb'] != '0': + if not source['id_imdb'].startswith('tt'): + title['imdb_id'] = 'tt' + source['id_imdb'].zfill(7) else: - logger.warning('No season detected for title %d', title_id) + title['imdb_id'] = source['id_imdb'] - # add title - titles[title_id] = title + # extract season + if title['type'] == 'episode': + if source['temporada'] and source['temporada'].isdigit(): + title['season'] = int(source['temporada']) + else: + match = season_re.search(source['dsc_nome_br']) + if match: + title['season'] = int(match.group('season')) + else: + logger.debug('No season detected for title %d (%s)', title_id, name) - logger.debug('Found %d titles', len(titles)) + # extract year + if year: + title['year'] = int(year) + elif source['dsc_data_lancamento'] and source['dsc_data_lancamento'].isdigit(): + # year is based on season air date hence the adjustment + title['year'] = int(source['dsc_data_lancamento']) - title.get('season', 1) + 1 + + # add title only if is valid + # Check against title without ignored chars + if self.is_valid_title(title, title_id, sanitized_titles[0], season, title_year): + titles[title_id] = title + + logger.debug('Found %d titles', len(titles)) return titles @region.cache_on_arguments(expiration_time=timedelta(minutes=15).total_seconds()) - def get_archives(self, title_id, language_code): - """Get the archive list from a given `title_id` and `language_code`. + def get_archives(self, title_id, language_code, title_type, season, episodes): + """Get the archive list from a given `title_id`, `language_code`, `title_type`, `season` and `episode`. :param int title_id: title id. :param int language_code: language code. + :param str title_type: episode or movie + :param int season: season + :param list episodes: episodes :return: the archives. :rtype: list of :class:`LegendasTVArchive` """ - logger.info('Getting archives for title %d and language %d', title_id, language_code) archives = [] - page = 1 + page = 0 while True: # get the archive page - url = self.server_url + 'util/carrega_legendas_busca_filme/{title}/{language}/-/{page}'.format( - title=title_id, language=language_code, page=page) + url = self.server_url + 'legenda/busca/-/{language}/-/{page}/{title}'.format( + language=language_code, page=page, title=title_id) r = self.session.get(url) - r.raise_for_status() + raise_for_status(r) # parse the results soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) - for archive_soup in soup.select('div.list_element > article > div'): + for archive_soup in soup.select('div.list_element > article > div > div.f_left'): # create archive - archive = LegendasTVArchive(archive_soup.a['href'].split('/')[2], archive_soup.a.text, - 'pack' in archive_soup['class'], 'destaque' in archive_soup['class'], + archive = LegendasTVArchive(archive_soup.a['href'].split('/')[2], + archive_soup.a.text, + 'pack' in archive_soup.parent['class'], + 'destaque' in archive_soup.parent['class'], self.server_url + archive_soup.a['href'][1:]) + # clean name of path separators and pack flags + clean_name = archive.name.replace('/', '-') + if archive.pack and clean_name.startswith('(p)'): + clean_name = clean_name[3:] + + # guess from name + guess = guessit(clean_name, {'type': title_type}) + + # episode + if season and episodes: + # discard mismatches on episode in non-pack archives + + # Guessit may return int for single episode or list for multi-episode + # Check if archive name has multiple episodes releases on it + if not archive.pack and 'episode' in guess: + wanted_episode = set(episodes) + archive_episode = set(ensure_list(guess['episode'])) + + if not wanted_episode.intersection(archive_episode): + logger.debug('Mismatched episode %s, discarding archive: %s', guess['episode'], clean_name) + continue # extract text containing downloads, rating and timestamp data_text = archive_soup.find('p', class_='data').text @@ -300,6 +383,8 @@ class LegendasTVProvider(Provider): raise ProviderError('Archive timestamp is in the future') # add archive + logger.info('Found archive for title %d and language %d at page %s: %s', + title_id, language_code, page, archive) archives.append(archive) # stop on last page @@ -322,7 +407,7 @@ class LegendasTVProvider(Provider): """ logger.info('Downloading archive %s', archive.id) r = self.session.get(self.server_url + 'downloadarquivo/{}'.format(archive.id)) - r.raise_for_status() + raise_for_status(r) # open the archive archive_stream = io.BytesIO(r.content) @@ -335,62 +420,28 @@ class LegendasTVProvider(Provider): else: raise ValueError('Not a valid archive') - def query(self, language, title, season=None, episode=None, year=None): + def query(self, language, title, season=None, episodes=None, year=None): # search for titles - titles = self.search_titles(sanitize(title)) - - # search for titles with the quote or dot character - ignore_characters = {'\'', '.'} - if any(c in title for c in ignore_characters): - titles.update(self.search_titles(sanitize(title, ignore_characters=ignore_characters))) + titles = self.search_titles(title, season, year) subtitles = [] # iterate over titles for title_id, t in titles.items(): - # discard mismatches on title - if sanitize(t['title']) != sanitize(title): - continue - # episode - if season and episode: - # discard mismatches on type - if t['type'] != 'episode': - continue - - # discard mismatches on season - if 'season' not in t or t['season'] != season: - continue - # movie - else: - # discard mismatches on type - if t['type'] != 'movie': - continue - - # discard mismatches on year - if year is not None and 'year' in t and t['year'] != year: - continue + logger.info('Getting archives for title %d and language %d', title_id, language.legendastv) + archives = self.get_archives(title_id, language.legendastv, t['type'], season, episodes or []) + if not archives: + logger.info('No archives found for title %d and language %d', title_id, language.legendastv) # iterate over title's archives - for a in self.get_archives(title_id, language.legendastv): - # clean name of path separators and pack flags - clean_name = a.name.replace('/', '-') - if a.pack and clean_name.startswith('(p)'): - clean_name = clean_name[3:] - - # guess from name - guess = guessit(clean_name, {'type': t['type']}) - - # episode - if season and episode: - # discard mismatches on episode in non-pack archives - if not a.pack and 'episode' in guess and guess['episode'] != episode: - continue + for a in archives: # compute an expiration time based on the archive timestamp expiration_time = (datetime.utcnow().replace(tzinfo=pytz.utc) - a.timestamp).total_seconds() # attempt to get the releases from the cache - releases = region.get(releases_key.format(archive_id=a.id), expiration_time=expiration_time) + cache_key = releases_key.format(archive_id=a.id, archive_name=a.name) + releases = region.get(cache_key, expiration_time=expiration_time) # the releases are not in cache or cache is expired if releases == NO_VALUE: @@ -417,27 +468,34 @@ class LegendasTVProvider(Provider): releases.append(name) # cache the releases - region.set(releases_key.format(archive_id=a.id), releases) + region.set(cache_key, releases) # iterate over releases for r in releases: - subtitle = LegendasTVSubtitle(language, t['type'], t['title'], t.get('year'), t.get('imdb_id'), - t.get('season'), a, r) + subtitle = self.subtitle_class(language, t['type'], t['title'], t.get('year'), t.get('imdb_id'), + t.get('season'), a, r) logger.debug('Found subtitle %r', subtitle) subtitles.append(subtitle) return subtitles def list_subtitles(self, video, languages): - season = episode = None + season = None + episodes = [] if isinstance(video, Episode): - title = video.series + titles = [video.series] + video.alternative_series season = video.season - episode = video.episode + episodes = video.episodes else: - title = video.title + titles = [video.title] + video.alternative_titles - return [s for l in languages for s in self.query(l, title, season=season, episode=episode, year=video.year)] + for title in titles: + subtitles = [s for l in languages for s in + self.query(l, title, season=season, episodes=episodes, year=video.year)] + if subtitles: + return subtitles + + return [] def download_subtitle(self, subtitle): # download archive in case we previously hit the releases cache and didn't download it @@ -446,3 +504,11 @@ class LegendasTVProvider(Provider): # extract subtitle's content subtitle.content = fix_line_ending(subtitle.archive.content.read(subtitle.name)) + + +def raise_for_status(r): + # When site is under maintaince and http status code 200. + if 'Em breve estaremos de volta' in r.text: + raise ServiceUnavailable + else: + r.raise_for_status() diff --git a/libs/common/subliminal/providers/napiprojekt.py b/libs/common/subliminal/providers/napiprojekt.py index f44f85d9..c766ff42 100644 --- a/libs/common/subliminal/providers/napiprojekt.py +++ b/libs/common/subliminal/providers/napiprojekt.py @@ -5,7 +5,6 @@ from babelfish import Language from requests import Session from . import Provider -from .. import __short_version__ from ..subtitle import Subtitle logger = logging.getLogger(__name__) @@ -42,11 +41,16 @@ class NapiProjektSubtitle(Subtitle): def __init__(self, language, hash): super(NapiProjektSubtitle, self).__init__(language) self.hash = hash + self.content = None @property def id(self): return self.hash + @property + def info(self): + return self.hash + def get_matches(self, video): matches = set() @@ -62,10 +66,14 @@ class NapiProjektProvider(Provider): languages = {Language.fromalpha2(l) for l in ['pl']} required_hash = 'napiprojekt' server_url = 'http://napiprojekt.pl/unit_napisy/dl.php' + subtitle_class = NapiProjektSubtitle + + def __init__(self): + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent def terminate(self): self.session.close() @@ -81,16 +89,16 @@ class NapiProjektProvider(Provider): 'f': hash, 't': get_subhash(hash)} logger.info('Searching subtitle %r', params) - response = self.session.get(self.server_url, params=params, timeout=10) - response.raise_for_status() + r = self.session.get(self.server_url, params=params, timeout=10) + r.raise_for_status() # handle subtitles not found and errors - if response.content[:4] == b'NPc0': + if r.content[:4] == b'NPc0': logger.debug('No subtitles found') return None - subtitle = NapiProjektSubtitle(language, hash) - subtitle.content = response.content + subtitle = self.subtitle_class(language, hash) + subtitle.content = r.content logger.debug('Found subtitle %r', subtitle) return subtitle diff --git a/libs/common/subliminal/providers/opensubtitles.py b/libs/common/subliminal/providers/opensubtitles.py index 5ab09da4..58b80e6c 100644 --- a/libs/common/subliminal/providers/opensubtitles.py +++ b/libs/common/subliminal/providers/opensubtitles.py @@ -11,9 +11,10 @@ from six.moves.xmlrpc_client import ServerProxy from . import Provider, TimeoutSafeTransport from .. import __short_version__ -from ..exceptions import AuthenticationError, ConfigurationError, DownloadLimitExceeded, ProviderError -from ..subtitle import Subtitle, fix_line_ending, guess_matches -from ..utils import sanitize +from ..exceptions import (AuthenticationError, ConfigurationError, DownloadLimitExceeded, ProviderError, + ServiceUnavailable) +from ..matches import guess_matches +from ..subtitle import Subtitle, fix_line_ending from ..video import Episode, Movie logger = logging.getLogger(__name__) @@ -26,7 +27,8 @@ class OpenSubtitlesSubtitle(Subtitle): def __init__(self, language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind, hash, movie_name, movie_release_name, movie_year, movie_imdb_id, series_season, series_episode, filename, encoding): - super(OpenSubtitlesSubtitle, self).__init__(language, hearing_impaired, page_link, encoding) + super(OpenSubtitlesSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, + page_link=page_link, encoding=encoding) self.subtitle_id = subtitle_id self.matched_by = matched_by self.movie_kind = movie_kind @@ -43,6 +45,14 @@ class OpenSubtitlesSubtitle(Subtitle): def id(self): return str(self.subtitle_id) + @property + def info(self): + if not self.filename and not self.movie_release_name: + return self.subtitle_id + if self.movie_release_name and len(self.movie_release_name) > len(self.filename): + return self.movie_release_name + return self.filename + @property def series_name(self): return self.series_re.match(self.movie_name).group('series_name') @@ -52,60 +62,39 @@ class OpenSubtitlesSubtitle(Subtitle): return self.series_re.match(self.movie_name).group('series_title') def get_matches(self, video): - matches = set() - - # episode - if isinstance(video, Episode) and self.movie_kind == 'episode': - # tag match, assume series, year, season and episode matches - if self.matched_by == 'tag': - matches |= {'series', 'year', 'season', 'episode'} - # series - if video.series and sanitize(self.series_name) == sanitize(video.series): - matches.add('series') - # year - if video.original_series and self.movie_year is None or video.year and video.year == self.movie_year: - matches.add('year') - # season - if video.season and self.series_season == video.season: - matches.add('season') - # episode - if video.episode and self.series_episode == video.episode: - matches.add('episode') - # title - if video.title and sanitize(self.series_title) == sanitize(video.title): - matches.add('title') - # guess - matches |= guess_matches(video, guessit(self.movie_release_name, {'type': 'episode'})) - matches |= guess_matches(video, guessit(self.filename, {'type': 'episode'})) - # hash - if 'opensubtitles' in video.hashes and self.hash == video.hashes['opensubtitles']: - if 'series' in matches and 'season' in matches and 'episode' in matches: - matches.add('hash') - else: - logger.debug('Match on hash discarded') - # movie - elif isinstance(video, Movie) and self.movie_kind == 'movie': - # tag match, assume title and year matches - if self.matched_by == 'tag': - matches |= {'title', 'year'} - # title - if video.title and sanitize(self.movie_name) == sanitize(video.title): - matches.add('title') - # year - if video.year and self.movie_year == video.year: - matches.add('year') - # guess - matches |= guess_matches(video, guessit(self.movie_release_name, {'type': 'movie'})) - matches |= guess_matches(video, guessit(self.filename, {'type': 'movie'})) - # hash - if 'opensubtitles' in video.hashes and self.hash == video.hashes['opensubtitles']: - if 'title' in matches: - matches.add('hash') - else: - logger.debug('Match on hash discarded') - else: + if (isinstance(video, Episode) and self.movie_kind != 'episode') or ( + isinstance(video, Movie) and self.movie_kind != 'movie'): logger.info('%r is not a valid movie_kind', self.movie_kind) - return matches + return set() + + matches = guess_matches(video, { + 'title': self.series_name if self.movie_kind == 'episode' else self.movie_name, + 'episode_title': self.series_title if self.movie_kind == 'episode' else None, + 'year': self.movie_year, + 'season': self.series_season, + 'episode': self.series_episode + }) + + # tag + if self.matched_by == 'tag': + if not video.imdb_id or self.movie_imdb_id == video.imdb_id: + if self.movie_kind == 'episode': + matches |= {'series', 'year', 'season', 'episode'} + elif self.movie_kind == 'movie': + matches |= {'title', 'year'} + + # guess + matches |= guess_matches(video, guessit(self.movie_release_name, {'type': self.movie_kind})) + matches |= guess_matches(video, guessit(self.filename, {'type': self.movie_kind})) + + # hash + if 'opensubtitles' in video.hashes and self.hash == video.hashes['opensubtitles']: + if self.movie_kind == 'movie' and 'title' in matches: + matches.add('hash') + elif self.movie_kind == 'episode' and 'series' in matches and 'season' in matches and 'episode' in matches: + matches.add('hash') + else: + logger.debug('Match on hash discarded') # imdb_id if video.imdb_id and self.movie_imdb_id == video.imdb_id: @@ -122,10 +111,13 @@ class OpenSubtitlesProvider(Provider): """ languages = {Language.fromopensubtitles(l) for l in language_converters['opensubtitles'].codes} + server_url = 'https://api.opensubtitles.org/xml-rpc' + subtitle_class = OpenSubtitlesSubtitle + user_agent = 'subliminal v%s' % __short_version__ def __init__(self, username=None, password=None): - self.server = ServerProxy('https://api.opensubtitles.org/xml-rpc', TimeoutSafeTransport(10)) - if username and not password or not username and password: + self.server = ServerProxy(self.server_url, TimeoutSafeTransport(10)) + if any((username, password)) and not all((username, password)): raise ConfigurationError('Username and password must be specified') # None values not allowed for logging in, so replace it by '' self.username = username or '' @@ -134,8 +126,7 @@ class OpenSubtitlesProvider(Provider): def initialize(self): logger.info('Logging in') - response = checked(self.server.LogIn(self.username, self.password, 'eng', - 'subliminal v%s' % __short_version__)) + response = checked(self.server.LogIn(self.username, self.password, 'eng', self.user_agent)) self.token = response['token'] logger.debug('Logged in with token %r', self.token) @@ -156,7 +147,10 @@ class OpenSubtitlesProvider(Provider): if hash and size: criteria.append({'moviehash': hash, 'moviebytesize': str(size)}) if imdb_id: - criteria.append({'imdbid': imdb_id[2:]}) + if season and episode: + criteria.append({'imdbid': imdb_id[2:], 'season': season, 'episode': episode}) + else: + criteria.append({'imdbid': imdb_id[2:]}) if tag: criteria.append({'tag': tag}) if query and season and episode: @@ -199,9 +193,9 @@ class OpenSubtitlesProvider(Provider): filename = subtitle_item['SubFileName'] encoding = subtitle_item.get('SubEncoding') or None - subtitle = OpenSubtitlesSubtitle(language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind, - hash, movie_name, movie_release_name, movie_year, movie_imdb_id, - series_season, series_episode, filename, encoding) + subtitle = self.subtitle_class(language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind, + hash, movie_name, movie_release_name, movie_year, movie_imdb_id, + series_season, series_episode, filename, encoding) logger.debug('Found subtitle %r by %s', subtitle, matched_by) subtitles.append(subtitle) @@ -225,6 +219,17 @@ class OpenSubtitlesProvider(Provider): subtitle.content = fix_line_ending(zlib.decompress(base64.b64decode(response['data'][0]['data']), 47)) +class OpenSubtitlesVipSubtitle(OpenSubtitlesSubtitle): + """OpenSubtitles Subtitle.""" + provider_name = 'opensubtitlesvip' + + +class OpenSubtitlesVipProvider(OpenSubtitlesProvider): + """OpenSubtitles Provider using VIP url.""" + server_url = 'https://vip-api.opensubtitles.org/xml-rpc' + subtitle_class = OpenSubtitlesVipSubtitle + + class OpenSubtitlesError(ProviderError): """Base class for non-generic :class:`OpenSubtitlesProvider` exceptions.""" pass @@ -260,11 +265,6 @@ class DisabledUserAgent(OpenSubtitlesError, AuthenticationError): pass -class ServiceUnavailable(OpenSubtitlesError): - """Exception raised when status is '503 Service Unavailable'.""" - pass - - def checked(response): """Check a response status before returning it. diff --git a/libs/common/subliminal/providers/podnapisi.py b/libs/common/subliminal/providers/podnapisi.py index f643682b..37fc425c 100644 --- a/libs/common/subliminal/providers/podnapisi.py +++ b/libs/common/subliminal/providers/podnapisi.py @@ -16,11 +16,10 @@ from requests import Session from zipfile import ZipFile from . import Provider -from .. import __short_version__ from ..exceptions import ProviderError -from ..subtitle import Subtitle, fix_line_ending, guess_matches -from ..utils import sanitize -from ..video import Episode, Movie +from ..matches import guess_matches +from ..subtitle import Subtitle, fix_line_ending +from ..video import Episode logger = logging.getLogger(__name__) @@ -31,7 +30,7 @@ class PodnapisiSubtitle(Subtitle): def __init__(self, language, hearing_impaired, page_link, pid, releases, title, season=None, episode=None, year=None): - super(PodnapisiSubtitle, self).__init__(language, hearing_impaired, page_link) + super(PodnapisiSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link) self.pid = pid self.releases = releases self.title = title @@ -43,37 +42,21 @@ class PodnapisiSubtitle(Subtitle): def id(self): return self.pid - def get_matches(self, video): - matches = set() + @property + def info(self): + return ' '.join(self.releases) or self.pid - # episode - if isinstance(video, Episode): - # series - if video.series and sanitize(self.title) == sanitize(video.series): - matches.add('series') - # year - if video.original_series and self.year is None or video.year and video.year == self.year: - matches.add('year') - # season - if video.season and self.season == video.season: - matches.add('season') - # episode - if video.episode and self.episode == video.episode: - matches.add('episode') - # guess - for release in self.releases: - matches |= guess_matches(video, guessit(release, {'type': 'episode'})) - # movie - elif isinstance(video, Movie): - # title - if video.title and sanitize(self.title) == sanitize(video.title): - matches.add('title') - # year - if video.year and self.year == video.year: - matches.add('year') - # guess - for release in self.releases: - matches |= guess_matches(video, guessit(release, {'type': 'movie'})) + def get_matches(self, video): + matches = guess_matches(video, { + 'title': self.title, + 'year': self.year, + 'season': self.season, + 'episode': self.episode + }) + + video_type = 'episode' if isinstance(video, Episode) else 'movie' + for release in self.releases: + matches |= guess_matches(video, guessit(release, {'type': video_type})) return matches @@ -82,11 +65,15 @@ class PodnapisiProvider(Provider): """Podnapisi Provider.""" languages = ({Language('por', 'BR'), Language('srp', script='Latn')} | {Language.fromalpha2(l) for l in language_converters['alpha2'].codes}) - server_url = 'http://podnapisi.net/subtitles/' + server_url = 'https://www.podnapisi.net/subtitles/' + subtitle_class = PodnapisiSubtitle + + def __init__(self): + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent def terminate(self): self.session.close() @@ -108,7 +95,9 @@ class PodnapisiProvider(Provider): pids = set() while True: # query the server - xml = etree.fromstring(self.session.get(self.server_url + 'search/old', params=params, timeout=10).content) + r = self.session.get(self.server_url + 'search/old', params=params, timeout=10) + r.raise_for_status() + xml = etree.fromstring(r.content) # exit if no results if not int(xml.find('pagination/results').text): @@ -118,10 +107,14 @@ class PodnapisiProvider(Provider): # loop over subtitles for subtitle_xml in xml.findall('subtitle'): # read xml elements + pid = subtitle_xml.find('pid').text + # ignore duplicates, see http://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164&start=10#p213321 + if pid in pids: + continue + language = Language.fromietf(subtitle_xml.find('language').text) hearing_impaired = 'n' in (subtitle_xml.find('flags').text or '') page_link = subtitle_xml.find('url').text - pid = subtitle_xml.find('pid').text releases = [] if subtitle_xml.find('release').text: for release in subtitle_xml.find('release').text.split(): @@ -134,15 +127,11 @@ class PodnapisiProvider(Provider): year = int(subtitle_xml.find('year').text) if is_episode: - subtitle = PodnapisiSubtitle(language, hearing_impaired, page_link, pid, releases, title, - season=season, episode=episode, year=year) + subtitle = self.subtitle_class(language, hearing_impaired, page_link, pid, releases, title, + season=season, episode=episode, year=year) else: - subtitle = PodnapisiSubtitle(language, hearing_impaired, page_link, pid, releases, title, - year=year) - - # ignore duplicates, see http://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164&start=10#p213321 - if pid in pids: - continue + subtitle = self.subtitle_class(language, hearing_impaired, page_link, pid, releases, title, + year=year) logger.debug('Found subtitle %r', subtitle) subtitles.append(subtitle) @@ -159,11 +148,21 @@ class PodnapisiProvider(Provider): return subtitles def list_subtitles(self, video, languages): + season = episode = None if isinstance(video, Episode): - return [s for l in languages for s in self.query(l, video.series, season=video.season, - episode=video.episode, year=video.year)] - elif isinstance(video, Movie): - return [s for l in languages for s in self.query(l, video.title, year=video.year)] + titles = [video.series] + video.alternative_series + season = video.season + episode = video.episode + else: + titles = [video.title] + video.alternative_titles + + for title in titles: + subtitles = [s for l in languages for s in + self.query(l, title, season=season, episode=episode, year=video.year)] + if subtitles: + return subtitles + + return [] def download_subtitle(self, subtitle): # download as a zip diff --git a/libs/common/subliminal/providers/shooter.py b/libs/common/subliminal/providers/shooter.py index fc79faf7..b52bdb34 100644 --- a/libs/common/subliminal/providers/shooter.py +++ b/libs/common/subliminal/providers/shooter.py @@ -7,7 +7,6 @@ from babelfish import Language, language_converters from requests import Session from . import Provider -from .. import __short_version__ from ..subtitle import Subtitle, fix_line_ending logger = logging.getLogger(__name__) @@ -28,6 +27,10 @@ class ShooterSubtitle(Subtitle): def id(self): return self.download_link + @property + def info(self): + return self.hash + def get_matches(self, video): matches = set() @@ -42,10 +45,14 @@ class ShooterProvider(Provider): """Shooter Provider.""" languages = {Language(l) for l in ['eng', 'zho']} server_url = 'https://www.shooter.cn/api/subapi.php' + subtitle_class = ShooterSubtitle + + def __init__(self): + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent def terminate(self): self.session.close() @@ -64,7 +71,7 @@ class ShooterProvider(Provider): # parse the subtitles results = json.loads(r.text) - subtitles = [ShooterSubtitle(language, hash, t['Link']) for s in results for t in s['Files']] + subtitles = [self.subtitle_class(language, hash, t['Link']) for s in results for t in s['Files']] return subtitles diff --git a/libs/common/subliminal/providers/subscenter.py b/libs/common/subliminal/providers/subscenter.py deleted file mode 100644 index 1e25e5e1..00000000 --- a/libs/common/subliminal/providers/subscenter.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- coding: utf-8 -*- -import bisect -from collections import defaultdict -import io -import json -import logging -import zipfile - -from babelfish import Language -from guessit import guessit -from requests import Session - -from . import ParserBeautifulSoup, Provider -from .. import __short_version__ -from ..cache import SHOW_EXPIRATION_TIME, region -from ..exceptions import AuthenticationError, ConfigurationError, ProviderError -from ..subtitle import Subtitle, fix_line_ending, guess_matches -from ..utils import sanitize -from ..video import Episode, Movie - -logger = logging.getLogger(__name__) - - -class SubsCenterSubtitle(Subtitle): - """SubsCenter Subtitle.""" - provider_name = 'subscenter' - - def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, subtitle_id, subtitle_key, - downloaded, releases): - super(SubsCenterSubtitle, self).__init__(language, hearing_impaired, page_link) - self.series = series - self.season = season - self.episode = episode - self.title = title - self.subtitle_id = subtitle_id - self.subtitle_key = subtitle_key - self.downloaded = downloaded - self.releases = releases - - @property - def id(self): - return str(self.subtitle_id) - - def get_matches(self, video): - matches = set() - - # episode - if isinstance(video, Episode): - # series - if video.series and sanitize(self.series) == sanitize(video.series): - matches.add('series') - # season - if video.season and self.season == video.season: - matches.add('season') - # episode - if video.episode and self.episode == video.episode: - matches.add('episode') - # guess - for release in self.releases: - matches |= guess_matches(video, guessit(release, {'type': 'episode'})) - # movie - elif isinstance(video, Movie): - # guess - for release in self.releases: - matches |= guess_matches(video, guessit(release, {'type': 'movie'})) - - # title - if video.title and sanitize(self.title) == sanitize(video.title): - matches.add('title') - - return matches - - -class SubsCenterProvider(Provider): - """SubsCenter Provider.""" - languages = {Language.fromalpha2(l) for l in ['he']} - server_url = 'http://www.subscenter.co/he/' - - def __init__(self, username=None, password=None): - if username is not None and password is None or username is None and password is not None: - raise ConfigurationError('Username and password must be specified') - - self.session = None - self.username = username - self.password = password - self.logged_in = False - - def initialize(self): - self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__) - - # login - if self.username is not None and self.password is not None: - logger.debug('Logging in') - url = self.server_url + 'subscenter/accounts/login/' - - # retrieve CSRF token - self.session.get(url) - csrf_token = self.session.cookies['csrftoken'] - - # actual login - data = {'username': self.username, 'password': self.password, 'csrfmiddlewaretoken': csrf_token} - r = self.session.post(url, data, allow_redirects=False, timeout=10) - - if r.status_code != 302: - raise AuthenticationError(self.username) - - logger.info('Logged in') - self.logged_in = True - - def terminate(self): - # logout - if self.logged_in: - logger.info('Logging out') - r = self.session.get(self.server_url + 'subscenter/accounts/logout/', timeout=10) - r.raise_for_status() - logger.info('Logged out') - self.logged_in = False - - self.session.close() - - @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME) - def _search_url_titles(self, title): - """Search the URL titles by kind for the given `title`. - - :param str title: title to search for. - :return: the URL titles by kind. - :rtype: collections.defaultdict - - """ - # make the search - logger.info('Searching title name for %r', title) - r = self.session.get(self.server_url + 'subtitle/search/', params={'q': title}, timeout=10) - r.raise_for_status() - - # check for redirections - if r.history and all([h.status_code == 302 for h in r.history]): - logger.debug('Redirected to the subtitles page') - links = [r.url] - else: - # get the suggestions (if needed) - soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) - links = [link.attrs['href'] for link in soup.select('#processes div.generalWindowTop a')] - logger.debug('Found %d suggestions', len(links)) - - url_titles = defaultdict(list) - for link in links: - parts = link.split('/') - url_titles[parts[-3]].append(parts[-2]) - - return url_titles - - def query(self, title, season=None, episode=None): - # search for the url title - url_titles = self._search_url_titles(title) - - # episode - if season and episode: - if 'series' not in url_titles: - logger.error('No URL title found for series %r', title) - return [] - url_title = url_titles['series'][0] - logger.debug('Using series title %r', url_title) - url = self.server_url + 'cst/data/series/sb/{}/{}/{}/'.format(url_title, season, episode) - page_link = self.server_url + 'subtitle/series/{}/{}/{}/'.format(url_title, season, episode) - else: - if 'movie' not in url_titles: - logger.error('No URL title found for movie %r', title) - return [] - url_title = url_titles['movie'][0] - logger.debug('Using movie title %r', url_title) - url = self.server_url + 'cst/data/movie/sb/{}/'.format(url_title) - page_link = self.server_url + 'subtitle/movie/{}/'.format(url_title) - - # get the list of subtitles - logger.debug('Getting the list of subtitles') - r = self.session.get(url) - r.raise_for_status() - results = json.loads(r.text) - - # loop over results - subtitles = {} - for language_code, language_data in results.items(): - for quality_data in language_data.values(): - for quality, subtitles_data in quality_data.items(): - for subtitle_item in subtitles_data.values(): - # read the item - language = Language.fromalpha2(language_code) - hearing_impaired = bool(subtitle_item['hearing_impaired']) - subtitle_id = subtitle_item['id'] - subtitle_key = subtitle_item['key'] - downloaded = subtitle_item['downloaded'] - release = subtitle_item['subtitle_version'] - - # add the release and increment downloaded count if we already have the subtitle - if subtitle_id in subtitles: - logger.debug('Found additional release %r for subtitle %d', release, subtitle_id) - bisect.insort_left(subtitles[subtitle_id].releases, release) # deterministic order - subtitles[subtitle_id].downloaded += downloaded - continue - - # otherwise create it - subtitle = SubsCenterSubtitle(language, hearing_impaired, page_link, title, season, episode, - title, subtitle_id, subtitle_key, downloaded, [release]) - logger.debug('Found subtitle %r', subtitle) - subtitles[subtitle_id] = subtitle - - return subtitles.values() - - def list_subtitles(self, video, languages): - season = episode = None - title = video.title - - if isinstance(video, Episode): - title = video.series - season = video.season - episode = video.episode - - return [s for s in self.query(title, season, episode) if s.language in languages] - - def download_subtitle(self, subtitle): - # download - url = self.server_url + 'subtitle/download/{}/{}/'.format(subtitle.language.alpha2, subtitle.subtitle_id) - params = {'v': subtitle.releases[0], 'key': subtitle.subtitle_key} - r = self.session.get(url, params=params, headers={'Referer': subtitle.page_link}, timeout=10) - r.raise_for_status() - - # open the zip - with zipfile.ZipFile(io.BytesIO(r.content)) as zf: - # remove some filenames from the namelist - namelist = [n for n in zf.namelist() if not n.endswith('.txt')] - if len(namelist) > 1: - raise ProviderError('More than one file to unzip') - - subtitle.content = fix_line_ending(zf.read(namelist[0])) diff --git a/libs/common/subliminal/providers/thesubdb.py b/libs/common/subliminal/providers/thesubdb.py index 6bf4a0eb..6f217642 100644 --- a/libs/common/subliminal/providers/thesubdb.py +++ b/libs/common/subliminal/providers/thesubdb.py @@ -25,6 +25,10 @@ class TheSubDBSubtitle(Subtitle): def id(self): return self.hash + '-' + str(self.language) + @property + def info(self): + return self.hash + def get_matches(self, video): matches = set() @@ -40,11 +44,15 @@ class TheSubDBProvider(Provider): languages = {Language.fromthesubdb(l) for l in language_converters['thesubdb'].codes} required_hash = 'thesubdb' server_url = 'http://api.thesubdb.com/' + subtitle_class = TheSubDBSubtitle + user_agent = 'SubDB/1.0 (subliminal/%s; https://github.com/Diaoul/subliminal)' % __short_version__ + + def __init__(self): + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = ('SubDB/1.0 (subliminal/%s; https://github.com/Diaoul/subliminal)' % - __short_version__) + self.session.headers['User-Agent'] = self.user_agent def terminate(self): self.session.close() @@ -66,7 +74,7 @@ class TheSubDBProvider(Provider): for language_code in r.text.split(','): language = Language.fromthesubdb(language_code) - subtitle = TheSubDBSubtitle(language, hash) + subtitle = self.subtitle_class(language, hash) logger.debug('Found subtitle %r', subtitle) subtitles.append(subtitle) diff --git a/libs/common/subliminal/providers/tvsubtitles.py b/libs/common/subliminal/providers/tvsubtitles.py index ec033ee7..bb267019 100644 --- a/libs/common/subliminal/providers/tvsubtitles.py +++ b/libs/common/subliminal/providers/tvsubtitles.py @@ -9,12 +9,10 @@ from guessit import guessit from requests import Session from . import ParserBeautifulSoup, Provider -from .. import __short_version__ from ..cache import EPISODE_EXPIRATION_TIME, SHOW_EXPIRATION_TIME, region from ..exceptions import ProviderError -from ..score import get_equivalent_release_groups -from ..subtitle import Subtitle, fix_line_ending, guess_matches -from ..utils import sanitize, sanitize_release_group +from ..matches import guess_matches +from ..subtitle import Subtitle, fix_line_ending from ..video import Episode logger = logging.getLogger(__name__) @@ -43,31 +41,24 @@ class TVsubtitlesSubtitle(Subtitle): def id(self): return str(self.subtitle_id) - def get_matches(self, video): - matches = set() + @property + def info(self): + return self.release or self.rip + + def get_matches(self, video): + matches = guess_matches(video, { + 'title': self.series, + 'season': self.season, + 'episode': self.episode, + 'year': self.year, + 'release_group': self.release + }) - # series - if video.series and sanitize(self.series) == sanitize(video.series): - matches.add('series') - # season - if video.season and self.season == video.season: - matches.add('season') - # episode - if video.episode and self.episode == video.episode: - matches.add('episode') - # year - if video.original_series and self.year is None or video.year and video.year == self.year: - matches.add('year') - # release_group - if (video.release_group and self.release and - any(r in sanitize_release_group(self.release) - for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))): - matches.add('release_group') # other properties if self.release: matches |= guess_matches(video, guessit(self.release, {'type': 'episode'}), partial=True) if self.rip: - matches |= guess_matches(video, guessit(self.rip), partial=True) + matches |= guess_matches(video, guessit(self.rip, {'type': 'episode'}), partial=True) return matches @@ -80,10 +71,14 @@ class TVsubtitlesProvider(Provider): ]} video_types = (Episode,) server_url = 'http://www.tvsubtitles.net/' + subtitle_class = TVsubtitlesSubtitle + + def __init__(self): + self.session = None def initialize(self): self.session = Session() - self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + self.session.headers['User-Agent'] = self.user_agent def terminate(self): self.session.close() @@ -158,13 +153,7 @@ class TVsubtitlesProvider(Provider): return episode_ids - def query(self, series, season, episode, year=None): - # search the show id - show_id = self.search_show_id(series, year) - if show_id is None: - logger.error('No show id found for %r (%r)', series, {'year': year}) - return [] - + def query(self, show_id, series, season, episode, year=None): # get the episode ids episode_ids = self.get_episode_ids(show_id, season) if episode not in episode_ids: @@ -184,9 +173,9 @@ class TVsubtitlesProvider(Provider): subtitle_id = int(row.parent['href'][10:-5]) page_link = self.server_url + 'subtitle-%d.html' % subtitle_id rip = row.find('p', title='rip').text.strip() or None - release = row.find('p', title='release').text.strip() or None + release = row.find('h5').text.strip() or None - subtitle = TVsubtitlesSubtitle(language, page_link, subtitle_id, series, season, episode, year, rip, + subtitle = self.subtitle_class(language, page_link, subtitle_id, series, season, episode, year, rip, release) logger.debug('Found subtitle %s', subtitle) subtitles.append(subtitle) @@ -194,7 +183,24 @@ class TVsubtitlesProvider(Provider): return subtitles def list_subtitles(self, video, languages): - return [s for s in self.query(video.series, video.season, video.episode, video.year) if s.language in languages] + # lookup show_id + titles = [video.series] + video.alternative_series + show_id = None + for title in titles: + show_id = self.search_show_id(title, video.year) + if show_id is not None: + break + + # query for subtitles with the show_id + if show_id is not None: + subtitles = [s for s in self.query(show_id, title, video.season, video.episode, video.year) + if s.language in languages and s.episode == video.episode] + if subtitles: + return subtitles + else: + logger.error('No show id found for %r (%r)', video.series, {'year': video.year}) + + return [] def download_subtitle(self, subtitle): # download as a zip diff --git a/libs/common/subliminal/refiners/hash.py b/libs/common/subliminal/refiners/hash.py new file mode 100644 index 00000000..88e31f5a --- /dev/null +++ b/libs/common/subliminal/refiners/hash.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +import logging + +from ..extensions import provider_manager, default_providers +from ..utils import hash_napiprojekt, hash_opensubtitles, hash_shooter, hash_thesubdb + +logger = logging.getLogger(__name__) + +hash_functions = { + 'napiprojekt': hash_napiprojekt, + 'opensubtitles': hash_opensubtitles, + 'opensubtitlesvip': hash_opensubtitles, + 'shooter': hash_shooter, + 'thesubdb': hash_thesubdb +} + + +def refine(video, providers=None, languages=None, **kwargs): + """Refine a video computing required hashes for the given providers. + + The following :class:`~subliminal.video.Video` attribute can be found: + + * :attr:`~subliminal.video.Video.hashes` + + """ + if video.size <= 10485760: + logger.warning('Size is lower than 10MB: hashes not computed') + return + + logger.debug('Computing hashes for %r', video.name) + for name in providers or default_providers: + provider = provider_manager[name].plugin + if name not in hash_functions: + continue + + if not provider.check_types(video): + continue + + if languages and not provider.check_languages(languages): + continue + + video.hashes[name] = hash_functions[name](video.name) + + logger.debug('Computed hashes %r', video.hashes) diff --git a/libs/common/subliminal/refiners/metadata.py b/libs/common/subliminal/refiners/metadata.py index a8408742..9e7a0a80 100644 --- a/libs/common/subliminal/refiners/metadata.py +++ b/libs/common/subliminal/refiners/metadata.py @@ -45,13 +45,13 @@ def refine(video, embedded_subtitles=True, **kwargs): # video codec if video_track.codec_id == 'V_MPEG4/ISO/AVC': - video.video_codec = 'h264' + video.video_codec = 'H.264' logger.debug('Found video_codec %s', video.video_codec) elif video_track.codec_id == 'V_MPEG4/ISO/SP': video.video_codec = 'DivX' logger.debug('Found video_codec %s', video.video_codec) elif video_track.codec_id == 'V_MPEG4/ISO/ASP': - video.video_codec = 'XviD' + video.video_codec = 'Xvid' logger.debug('Found video_codec %s', video.video_codec) else: logger.warning('MKV has no video track') @@ -61,7 +61,7 @@ def refine(video, embedded_subtitles=True, **kwargs): audio_track = mkv.audio_tracks[0] # audio codec if audio_track.codec_id == 'A_AC3': - video.audio_codec = 'AC3' + video.audio_codec = 'Dolby Digital' logger.debug('Found audio_codec %s', video.audio_codec) elif audio_track.codec_id == 'A_DTS': video.audio_codec = 'DTS' diff --git a/libs/common/subliminal/refiners/omdb.py b/libs/common/subliminal/refiners/omdb.py index e2514ae9..029d216e 100644 --- a/libs/common/subliminal/refiners/omdb.py +++ b/libs/common/subliminal/refiners/omdb.py @@ -7,7 +7,6 @@ import requests from .. import __short_version__ from ..cache import REFINER_EXPIRATION_TIME, region from ..video import Episode, Movie -from ..utils import sanitize logger = logging.getLogger(__name__) @@ -68,7 +67,8 @@ class OMDBClient(object): return j -omdb_client = OMDBClient(headers={'User-Agent': 'Subliminal/%s' % __short_version__}) +user_agent = 'Subliminal/%s' % __short_version__ +omdb_client = OMDBClient(headers={'User-Agent': user_agent}) @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME) @@ -89,7 +89,7 @@ def search(title, type, year): return all_results -def refine(video, **kwargs): +def refine(video, apikey=None, **kwargs): """Refine a video by searching `OMDb API `_. Several :class:`~subliminal.video.Episode` attributes can be found: @@ -105,6 +105,12 @@ def refine(video, **kwargs): * :attr:`~subliminal.video.Video.imdb_id` """ + if not apikey: + logger.warning('No apikey. Skipping omdb refiner.') + return + + omdb_client.session.params['apikey'] = apikey + if isinstance(video, Episode): # exit if the information is complete if video.series_imdb_id: @@ -119,7 +125,7 @@ def refine(video, **kwargs): logger.debug('Found %d results', len(results)) # filter the results - results = [r for r in results if sanitize(r['Title']) == sanitize(video.series)] + results = [r for r in results if video.matches(r['Title'])] if not results: logger.warning('No matching series found') return @@ -154,12 +160,12 @@ def refine(video, **kwargs): # search the movie results = search(video.title, 'movie', video.year) if not results: - logger.warning('No results') + logger.warning('No results for movie') return logger.debug('Found %d results', len(results)) # filter the results - results = [r for r in results if sanitize(r['Title']) == sanitize(video.title)] + results = [r for r in results if video.matches(r['Title'])] if not results: logger.warning('No matching movie found') return diff --git a/libs/common/subliminal/refiners/tvdb.py b/libs/common/subliminal/refiners/tvdb.py index 1828e5cf..84097f8d 100644 --- a/libs/common/subliminal/refiners/tvdb.py +++ b/libs/common/subliminal/refiners/tvdb.py @@ -4,6 +4,8 @@ from functools import wraps import logging import re +from babelfish import Country +import guessit import requests from .. import __short_version__ @@ -190,8 +192,14 @@ class TVDBClient(object): return r.json()['data'] +#: User-Agent to use +user_agent = 'Subliminal/%s' % __short_version__ + #: Configured instance of :class:`TVDBClient` -tvdb_client = TVDBClient('5EC930FB90DA1ADA', headers={'User-Agent': 'Subliminal/%s' % __short_version__}) +tvdb_client = TVDBClient('5EC930FB90DA1ADA', headers={'User-Agent': user_agent}) + +#: Configure guessit in order to use GuessitCountryConverter +guessit.api.configure() @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME) @@ -294,21 +302,33 @@ def refine(video, **kwargs): # iterate over series names for series_name in series_names: - # parse as series and year + # parse as series, year and country series, year, country = series_re.match(series_name).groups() if year: year = int(year) + if country: + country = Country.fromguessit(country) + # discard mismatches on year if year and (video.original_series or video.year != year): logger.debug('Discarding series name %r mismatch on year %d', series, year) continue + # discard mismatches on country + if video.country and video.country != country: + logger.debug('Discarding series name %r mismatch on country %r', series, country) + continue + # match on sanitized series name if sanitize(series) == sanitize(video.series): logger.debug('Found exact match on series %r', series_name) - matching_result['match'] = {'series': original_match['series'], 'year': series_year, - 'original_series': original_match['year'] is None} + matching_result['match'] = { + 'series': original_match['series'], + 'year': series_year or year, + 'country': country, + 'original_series': original_match['year'] is None and country is None + } break # add the result on match @@ -331,7 +351,9 @@ def refine(video, **kwargs): # add series information logger.debug('Found series %r', series) video.series = matching_result['match']['series'] + video.alternative_series.extend(series['aliases']) video.year = matching_result['match']['year'] + video.country = matching_result['match']['country'] video.original_series = matching_result['match']['original_series'] video.series_tvdb_id = series['id'] video.series_imdb_id = series['imdbId'] or None diff --git a/libs/common/subliminal/score.py b/libs/common/subliminal/score.py index 31ccb343..842ff1ef 100644 --- a/libs/common/subliminal/score.py +++ b/libs/common/subliminal/score.py @@ -13,11 +13,13 @@ Available matches: * hash * title * year + * country * series * season * episode * release_group - * format + * streaming_service + * source * audio_codec * resolution * hearing_impaired @@ -36,15 +38,19 @@ logger = logging.getLogger(__name__) #: Scores for episodes -episode_scores = {'hash': 359, 'series': 180, 'year': 90, 'season': 30, 'episode': 30, 'release_group': 15, - 'format': 7, 'audio_codec': 3, 'resolution': 2, 'video_codec': 2, 'hearing_impaired': 1} +episode_scores = {'hash': 809, 'series': 405, 'year': 135, 'country': 135, 'season': 45, 'episode': 45, + 'release_group': 15, 'streaming_service': 15, 'source': 7, 'audio_codec': 3, 'resolution': 2, + 'video_codec': 2, 'hearing_impaired': 1} #: Scores for movies -movie_scores = {'hash': 119, 'title': 60, 'year': 30, 'release_group': 15, - 'format': 7, 'audio_codec': 3, 'resolution': 2, 'video_codec': 2, 'hearing_impaired': 1} +movie_scores = {'hash': 269, 'title': 135, 'year': 45, 'country': 45, 'release_group': 15, 'streaming_service': 15, + 'source': 7, 'audio_codec': 3, 'resolution': 2, 'video_codec': 2, 'hearing_impaired': 1} + +#: All scores names +score_keys = set([s for s in episode_scores.keys()] + [s for s in movie_scores.keys()]) #: Equivalent release groups -equivalent_release_groups = ({'LOL', 'DIMENSION'}, {'ASAP', 'IMMERSE', 'FLEET'}) +equivalent_release_groups = ({'LOL', 'DIMENSION'}, {'ASAP', 'IMMERSE', 'FLEET'}, {'AVS', 'SVA'}) def get_equivalent_release_groups(release_group): @@ -118,20 +124,20 @@ def compute_score(subtitle, video, hearing_impaired=None): matches.add('episode') if 'series_imdb_id' in matches: logger.debug('Adding series_imdb_id match equivalent') - matches |= {'series', 'year'} + matches |= {'series', 'year', 'country'} if 'imdb_id' in matches: logger.debug('Adding imdb_id match equivalents') - matches |= {'series', 'year', 'season', 'episode'} + matches |= {'series', 'year', 'country', 'season', 'episode'} if 'tvdb_id' in matches: logger.debug('Adding tvdb_id match equivalents') - matches |= {'series', 'year', 'season', 'episode'} + matches |= {'series', 'year', 'country', 'season', 'episode'} if 'series_tvdb_id' in matches: logger.debug('Adding series_tvdb_id match equivalents') - matches |= {'series', 'year'} + matches |= {'series', 'year', 'country'} elif isinstance(video, Movie): if 'imdb_id' in matches: logger.debug('Adding imdb_id match equivalents') - matches |= {'title', 'year'} + matches |= {'title', 'year', 'country'} # handle hearing impaired if hearing_impaired is not None and subtitle.hearing_impaired == hearing_impaired: @@ -151,31 +157,41 @@ def compute_score(subtitle, video, hearing_impaired=None): def solve_episode_equations(): from sympy import Eq, solve, symbols - hash, series, year, season, episode, release_group = symbols('hash series year season episode release_group') - format, audio_codec, resolution, video_codec = symbols('format audio_codec resolution video_codec') + hash, series, year, country, season, episode = symbols('hash series year country season episode') + release_group, streaming_service, source = symbols('release_group streaming_service source') + audio_codec, resolution, video_codec = symbols('audio_codec resolution video_codec') hearing_impaired = symbols('hearing_impaired') equations = [ # hash is best - Eq(hash, series + year + season + episode + release_group + format + audio_codec + resolution + video_codec), + Eq(hash, series + year + country + season + episode + + release_group + streaming_service + source + audio_codec + resolution + video_codec), # series counts for the most part in the total score - Eq(series, year + season + episode + release_group + format + audio_codec + resolution + video_codec + 1), + Eq(series, year + country + season + episode + release_group + streaming_service + source + + audio_codec + resolution + video_codec + 1), # year is the second most important part - Eq(year, season + episode + release_group + format + audio_codec + resolution + video_codec + 1), + Eq(year, season + episode + release_group + streaming_service + source + + audio_codec + resolution + video_codec + 1), + + # year counts as much as country + Eq(year, country), # season is important too - Eq(season, release_group + format + audio_codec + resolution + video_codec + 1), + Eq(season, release_group + streaming_service + source + audio_codec + resolution + video_codec + 1), # episode is equally important to season Eq(episode, season), # release group is the next most wanted match - Eq(release_group, format + audio_codec + resolution + video_codec + 1), + Eq(release_group, source + audio_codec + resolution + video_codec + 1), - # format counts as much as audio_codec, resolution and video_codec - Eq(format, audio_codec + resolution + video_codec), + # streaming service counts as much as release group + Eq(release_group, streaming_service), + + # source counts as much as audio_codec, resolution and video_codec + Eq(source, audio_codec + resolution + video_codec), # audio_codec is more valuable than video_codec Eq(audio_codec, video_codec + 1), @@ -190,32 +206,40 @@ def solve_episode_equations(): Eq(hearing_impaired, 1), ] - return solve(equations, [hash, series, year, season, episode, release_group, format, audio_codec, resolution, - hearing_impaired, video_codec]) + return solve(equations, [hash, series, year, country, season, episode, release_group, streaming_service, source, + audio_codec, resolution, hearing_impaired, video_codec]) def solve_movie_equations(): from sympy import Eq, solve, symbols - hash, title, year, release_group = symbols('hash title year release_group') - format, audio_codec, resolution, video_codec = symbols('format audio_codec resolution video_codec') - hearing_impaired = symbols('hearing_impaired') + hash, title, year, country, release_group = symbols('hash title year country release_group') + streaming_service, source, audio_codec, resolution = symbols('streaming_service source audio_codec resolution') + video_codec, hearing_impaired = symbols('video_codec hearing_impaired') equations = [ # hash is best - Eq(hash, title + year + release_group + format + audio_codec + resolution + video_codec), + Eq(hash, title + year + country + release_group + streaming_service + + source + audio_codec + resolution + video_codec), # title counts for the most part in the total score - Eq(title, year + release_group + format + audio_codec + resolution + video_codec + 1), + Eq(title, year + country + release_group + streaming_service + + source + audio_codec + resolution + video_codec + 1), # year is the second most important part - Eq(year, release_group + format + audio_codec + resolution + video_codec + 1), + Eq(year, release_group + streaming_service + source + audio_codec + resolution + video_codec + 1), + + # year counts as much as country + Eq(year, country), # release group is the next most wanted match - Eq(release_group, format + audio_codec + resolution + video_codec + 1), + Eq(release_group, source + audio_codec + resolution + video_codec + 1), - # format counts as much as audio_codec, resolution and video_codec - Eq(format, audio_codec + resolution + video_codec), + # streaming service counts as much as release group + Eq(release_group, streaming_service), + + # source counts as much as audio_codec, resolution and video_codec + Eq(source, audio_codec + resolution + video_codec), # audio_codec is more valuable than video_codec Eq(audio_codec, video_codec + 1), @@ -230,5 +254,5 @@ def solve_movie_equations(): Eq(hearing_impaired, 1), ] - return solve(equations, [hash, title, year, release_group, format, audio_codec, resolution, hearing_impaired, - video_codec]) + return solve(equations, [hash, title, year, country, release_group, streaming_service, source, audio_codec, + resolution, hearing_impaired, video_codec]) diff --git a/libs/common/subliminal/subtitle.py b/libs/common/subliminal/subtitle.py index 60cdf3d6..54f76f61 100644 --- a/libs/common/subliminal/subtitle.py +++ b/libs/common/subliminal/subtitle.py @@ -6,10 +6,7 @@ import os import chardet import pysrt -from .score import get_equivalent_release_groups -from .video import Episode, Movie -from .utils import sanitize, sanitize_release_group - +from six import text_type logger = logging.getLogger(__name__) @@ -60,6 +57,11 @@ class Subtitle(object): """Unique identifier of the subtitle""" raise NotImplementedError + @property + def info(self): + """Info of the subtitle, human readable. Usually the subtitle name for GUI rendering""" + raise NotImplementedError + @property def text(self): """Content as string @@ -70,10 +72,17 @@ class Subtitle(object): if not self.content: return - if self.encoding: - return self.content.decode(self.encoding, errors='replace') + if not isinstance(self.content, text_type): + if self.encoding: + return self.content.decode(self.encoding, errors='replace') - return self.content.decode(self.guess_encoding(), errors='replace') + guessed_encoding = self.guess_encoding() + if guessed_encoding: + return self.content.decode(guessed_encoding, errors='replace') + + return None + + return self.content def is_valid(self): """Check if a :attr:`text` is a valid SubRip format. @@ -145,6 +154,18 @@ class Subtitle(object): return encoding + def get_path(self, video, single=False): + """Get the subtitle path using the `video`, `language` and `extension`. + + :param video: path to the video. + :type video: :class:`~subliminal.video.Video` + :param bool single: save a single subtitle, default is to save one subtitle per language. + :return: path of the subtitle. + :rtype: str + + """ + return get_subtitle_path(video.name, None if single else self.language) + def get_matches(self, video): """Get the matches against the `video`. @@ -182,68 +203,6 @@ def get_subtitle_path(video_path, language=None, extension='.srt'): return subtitle_root + extension -def guess_matches(video, guess, partial=False): - """Get matches between a `video` and a `guess`. - - If a guess is `partial`, the absence information won't be counted as a match. - - :param video: the video. - :type video: :class:`~subliminal.video.Video` - :param guess: the guess. - :type guess: dict - :param bool partial: whether or not the guess is partial. - :return: matches between the `video` and the `guess`. - :rtype: set - - """ - matches = set() - if isinstance(video, Episode): - # series - if video.series and 'title' in guess and sanitize(guess['title']) == sanitize(video.series): - matches.add('series') - # title - if video.title and 'episode_title' in guess and sanitize(guess['episode_title']) == sanitize(video.title): - matches.add('title') - # season - if video.season and 'season' in guess and guess['season'] == video.season: - matches.add('season') - # episode - if video.episode and 'episode' in guess and guess['episode'] == video.episode: - matches.add('episode') - # year - if video.year and 'year' in guess and guess['year'] == video.year: - matches.add('year') - # count "no year" as an information - if not partial and video.original_series and 'year' not in guess: - matches.add('year') - elif isinstance(video, Movie): - # year - if video.year and 'year' in guess and guess['year'] == video.year: - matches.add('year') - # title - if video.title and 'title' in guess and sanitize(guess['title']) == sanitize(video.title): - matches.add('title') - # release_group - if (video.release_group and 'release_group' in guess and - sanitize_release_group(guess['release_group']) in - get_equivalent_release_groups(sanitize_release_group(video.release_group))): - matches.add('release_group') - # resolution - if video.resolution and 'screen_size' in guess and guess['screen_size'] == video.resolution: - matches.add('resolution') - # format - if video.format and 'format' in guess and guess['format'].lower() == video.format.lower(): - matches.add('format') - # video_codec - if video.video_codec and 'video_codec' in guess and guess['video_codec'] == video.video_codec: - matches.add('video_codec') - # audio_codec - if video.audio_codec and 'audio_codec' in guess and guess['audio_codec'] == video.audio_codec: - matches.add('audio_codec') - - return matches - - def fix_line_ending(content): """Fix line ending of `content` by changing it to \n. diff --git a/libs/common/subliminal/subtitles/__init__.py b/libs/common/subliminal/subtitles/__init__.py deleted file mode 100644 index 2bddfe0f..00000000 --- a/libs/common/subliminal/subtitles/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import time - - -class Component(object): - """Base class for cue text. - - :param list components: sub-components of this one. - - """ - tag_name = 'Component' - - def __init__(self, components=None): - if components is None: - self.components = [] - elif isinstance(components, list): - self.components = components - else: - self.components = [components] - - def __iter__(self): - return iter(self.components) - - def __len__(self): - return len(self.components) - - def __str__(self): - return ''.join(str(c) for c in self.components) - - def __repr__(self): - return '<{name}>{components}'.format(name=self.tag_name, - components=''.join(repr(c) for c in self.components)) - - -class Bold(Component): - """Bold :class:`Component`.""" - tag_name = 'b' - - -class Italic(Component): - """Italic :class:`Component`.""" - tag_name = 'i' - - -class Underline(Component): - """Underline :class:`Component`.""" - tag_name = 'u' - - -class Strikethrough(Component): - """Strikethrough :class:`Component`.""" - tag_name = 's' - - -class Font(Component): - """Font :class:`Component`.""" - tag_name = 'font' - - def __init__(self, color, *args, **kwargs): - super(Font, self).__init__(*args, **kwargs) - self.color = color - - def __repr__(self): - return '<{name} "{color}">{components}'.format(name=self.tag_name, color=self.color, - components=''.join(repr(c) for c in self.components)) - - -class Cue(object): - """A single subtitle cue with timings and components. - - :param datetime.time start_time: start time. - :param datetime.time end_time: end time. - :param list components: cue components. - - """ - def __init__(self, start_time, end_time, components): - self.start_time = start_time - self.end_time = end_time - self.components = components - - def __repr__(self): - return '{end_time}] "{text}">'.format(start_time=self.start_time, end_time=self.end_time, - text=''.join(repr(c) for c in self.components)) - - -if __name__ == '__main__': - cue = Cue(time(), time(1), [Bold('Hello')]) - print repr(cue) diff --git a/libs/common/subliminal/subtitles/subrip.py b/libs/common/subliminal/subtitles/subrip.py deleted file mode 100644 index 47e5b477..00000000 --- a/libs/common/subliminal/subtitles/subrip.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -import re -from datetime import time - -from subliminal.subtitles import Cue - -index_re = re.compile(r'(?P\d+)') -timing_re = re.compile(r'(?P\d{2}):(?P\d{2}):(?P\d{2}),(?P\d{3})') - - -class SubripReadError(Exception): - pass - - -class SubripReadIndexError(SubripReadError): - pass - - -class SubripReader(object): - INDEX = 1 - TIMINGS = 2 - TEXT = 3 - - def __init__(self): - self.state = self.INDEX - - def read(self, content): - pass - - def read_line(self, line): - if self.state == self.INDEX: - if index_re.match(line): - raise SubripReadIndexError - - -def read_cue(stream): - """Attempt to parse a complete Cue from the stream""" - # skip blank lines - line = '' - while not line: - line = stream.readline() - - # parse index - if not index_re.match(line): - raise SubripReadIndexError - - # parse timings - line = stream.readline() - if '-->' not in line: - raise SubripReadError - timings = line.split('-->') - if not len(timings): - raise SubripReadError - - # parse start time - match = timing_re.match(timings[0].strip()) - if not match: - raise SubripReadError - start_time = time(**match.groupdict()) - - # parse end time - match = timing_re.match(timings[0].strip()) - if not match: - raise SubripReadError - end_time = time(**match.groupdict()) - - - - -class SubripSubtitle(object): - def __init__(self): - self.cues = [] - - -if __name__ == '__main__': - print read_cue('toto') - i = 0 - for x in read_cue('toto'): - print x - if i > 10: - break - i += 1 diff --git a/libs/common/subliminal/utils.py b/libs/common/subliminal/utils.py index ac426d45..6f90c806 100644 --- a/libs/common/subliminal/utils.py +++ b/libs/common/subliminal/utils.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- +import logging from datetime import datetime import hashlib import os import re +import socket import struct +import requests +from requests.exceptions import SSLError +from six.moves.xmlrpc_client import ProtocolError + +from .exceptions import ServiceUnavailable + + +logger = logging.getLogger(__name__) + def hash_opensubtitles(video_path): """Compute a hash using OpenSubtitles' algorithm. @@ -106,7 +117,7 @@ def sanitize(string, ignore_characters=None): ignore_characters = ignore_characters or set() # replace some characters with one space - characters = {'-', ':', '(', ')', '.'} - ignore_characters + characters = {'-', ':', '(', ')', '.', ','} - ignore_characters if characters: string = re.sub(r'[%s]' % re.escape(''.join(characters)), ' ', string) @@ -150,3 +161,48 @@ def timestamp(date): """ return (date - datetime(1970, 1, 1)).total_seconds() + + +def matches_title(actual, title, alternative_titles): + """Whether `actual` matches the `title` or `alternative_titles` + + :param str actual: the actual title to check + :param str title: the expected title + :param list alternative_titles: the expected alternative_titles + :return: whether the actual title matches the title or alternative_titles. + :rtype: bool + + """ + actual = sanitize(actual) + title = sanitize(title) + if actual == title: + return True + + alternative_titles = set(sanitize(t) for t in alternative_titles) + if actual in alternative_titles: + return True + + return actual.startswith(title) and actual[len(title):].strip() in alternative_titles + + +def handle_exception(e, msg): + """Handle exception, logging the proper error message followed by `msg`. + + Exception traceback is only logged for specific cases. + + :param exception e: The exception to handle. + :param str msg: The message to log. + """ + if isinstance(e, (requests.Timeout, socket.timeout)): + logger.error('Request timed out. %s', msg) + elif isinstance(e, (ServiceUnavailable, ProtocolError)): + # OpenSubtitles raises xmlrpclib.ProtocolError when unavailable + logger.error('Service unavailable. %s', msg) + elif isinstance(e, requests.exceptions.HTTPError): + logger.error('HTTP error %r. %s', e.response.status_code, msg, + exc_info=e.response.status_code not in range(500, 600)) + elif isinstance(e, SSLError): + logger.error('SSL error %r. %s', e.args[0], msg, + exc_info=e.args[0] != 'The read operation timed out') + else: + logger.exception('Unexpected error. %s', msg) diff --git a/libs/common/subliminal/video.py b/libs/common/subliminal/video.py index 00304e91..e5e04e4f 100644 --- a/libs/common/subliminal/video.py +++ b/libs/common/subliminal/video.py @@ -5,6 +5,9 @@ import logging import os from guessit import guessit +from rebulk.loose import ensure_list + +from subliminal.utils import matches_title logger = logging.getLogger(__name__) @@ -12,10 +15,10 @@ logger = logging.getLogger(__name__) VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik', '.bix', '.box', '.cam', '.dat', '.divx', '.dmf', '.dv', '.dvr-ms', '.evo', '.flc', '.fli', '.flic', '.flv', '.flx', '.gvi', '.gvp', '.h264', '.m1v', '.m2p', '.m2ts', '.m2v', '.m4e', - '.m4v', '.mjp', '.mjpeg', '.mjpg', '.mkv', '.moov', '.mov', '.movhd', '.movie', '.movx', '.mp4', - '.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm' '.ogv', '.omf', - '.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo', - '.vob', '.vro', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid') + '.m4v', '.mjp', '.mjpeg', '.mjpg', '.mk3d', '.mkv', '.moov', '.mov', '.movhd', '.movie', '.movx', + '.mp4', '.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm', '.ogv', + '.omf', '.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', + '.vivo', '.vob', '.vro', '.webm', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid') class Video(object): @@ -24,8 +27,9 @@ class Video(object): Represent a video, existing or not. :param str name: name or path of the video. - :param str format: format of the video (HDTV, WEB-DL, BluRay, ...). + :param str source: source of the video (HDTV, Web, Blu-ray, ...). :param str release_group: release group of the video. + :param str streaming_service: streaming_service of the video. :param str resolution: resolution of the video stream (480p, 720p, 1080p or 1080i). :param str video_codec: codec of the video stream. :param str audio_codec: codec of the main audio stream. @@ -35,17 +39,20 @@ class Video(object): :param set subtitle_languages: existing subtitle languages. """ - def __init__(self, name, format=None, release_group=None, resolution=None, video_codec=None, audio_codec=None, - imdb_id=None, hashes=None, size=None, subtitle_languages=None): + def __init__(self, name, source=None, release_group=None, resolution=None, streaming_service=None, + video_codec=None, audio_codec=None, imdb_id=None, hashes=None, size=None, subtitle_languages=None): #: Name or path of the video self.name = name - #: Format of the video (HDTV, WEB-DL, BluRay, ...) - self.format = format + #: Source of the video (HDTV, Web, Blu-ray, ...) + self.source = source #: Release group of the video self.release_group = release_group + #: Streaming service of the video + self.streaming_service = streaming_service + #: Resolution of the video stream (480p, 720p, 1080p or 1080i) self.resolution = resolution @@ -118,16 +125,19 @@ class Episode(Video): :param str series: series of the episode. :param int season: season number of the episode. - :param int episode: episode number of the episode. + :param int or list episodes: episode numbers of the episode. :param str title: title of the episode. :param int year: year of the series. + :param country: Country of the series. + :type country: :class:`~babelfish.country.Country` :param bool original_series: whether the series is the first with this name. :param int tvdb_id: TVDB id of the episode. + :param list alternative_series: alternative names of the series :param \*\*kwargs: additional parameters for the :class:`Video` constructor. """ - def __init__(self, name, series, season, episode, title=None, year=None, original_series=True, tvdb_id=None, - series_tvdb_id=None, series_imdb_id=None, **kwargs): + def __init__(self, name, series, season, episodes, title=None, year=None, country=None, original_series=True, + tvdb_id=None, series_tvdb_id=None, series_imdb_id=None, alternative_series=None, **kwargs): super(Episode, self).__init__(name, **kwargs) #: Series of the episode @@ -136,8 +146,8 @@ class Episode(Video): #: Season number of the episode self.season = season - #: Episode number of the episode - self.episode = episode + #: Episode numbers of the episode + self.episodes = ensure_list(episodes) #: Title of the episode self.title = title @@ -148,6 +158,9 @@ class Episode(Video): #: The series is the first with this name self.original_series = original_series + #: Country of the series + self.country = country + #: TVDB id of the episode self.tvdb_id = tvdb_id @@ -157,6 +170,16 @@ class Episode(Video): #: IMDb id of the series self.series_imdb_id = series_imdb_id + #: Alternative names of the series + self.alternative_series = alternative_series or [] + + @property + def episode(self): + return min(self.episodes) if self.episodes else None + + def matches(self, series): + return matches_title(series, self.series, self.alternative_series) + @classmethod def fromguess(cls, name, guess): if guess['type'] != 'episode': @@ -165,9 +188,12 @@ class Episode(Video): if 'title' not in guess or 'episode' not in guess: raise ValueError('Insufficient data to process the guess') - return cls(name, guess['title'], guess.get('season', 1), guess['episode'], title=guess.get('episode_title'), - year=guess.get('year'), format=guess.get('format'), original_series='year' not in guess, - release_group=guess.get('release_group'), resolution=guess.get('screen_size'), + return cls(name, guess['title'], guess.get('season', 1), guess.get('episode'), title=guess.get('episode_title'), + year=guess.get('year'), country=guess.get('country'), + original_series='year' not in guess and 'country' not in guess, + source=guess.get('source'), alternative_series=ensure_list(guess.get('alternative_title')), + release_group=guess.get('release_group'), streaming_service=guess.get('streaming_service'), + resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec')) @classmethod @@ -175,10 +201,13 @@ class Episode(Video): return cls.fromguess(name, guessit(name, {'type': 'episode'})) def __repr__(self): - if self.year is None: - return '<%s [%r, %dx%d]>' % (self.__class__.__name__, self.series, self.season, self.episode) - - return '<%s [%r, %d, %dx%d]>' % (self.__class__.__name__, self.series, self.year, self.season, self.episode) + return '<{cn} [{series}{open}{country}{sep}{year}{close} s{season:02d}e{episodes}]>'.format( + cn=self.__class__.__name__, series=self.series, year=self.year or '', country=self.country or '', + season=self.season, episodes='-'.join(map(lambda v: '{:02d}'.format(v), self.episodes)), + open=' (' if not self.original_series else '', + sep=') (' if self.year and self.country else '', + close=')' if not self.original_series else '' + ) class Movie(Video): @@ -186,10 +215,13 @@ class Movie(Video): :param str title: title of the movie. :param int year: year of the movie. + :param country: Country of the movie. + :type country: :class:`~babelfish.country.Country` + :param list alternative_titles: alternative titles of the movie :param \*\*kwargs: additional parameters for the :class:`Video` constructor. """ - def __init__(self, name, title, year=None, **kwargs): + def __init__(self, name, title, year=None, country=None, alternative_titles=None, **kwargs): super(Movie, self).__init__(name, **kwargs) #: Title of the movie @@ -198,6 +230,15 @@ class Movie(Video): #: Year of the movie self.year = year + #: Country of the movie + self.country = country + + #: Alternative titles of the movie + self.alternative_titles = alternative_titles or [] + + def matches(self, title): + return matches_title(title, self.title, self.alternative_titles) + @classmethod def fromguess(cls, name, guess): if guess['type'] != 'movie': @@ -206,16 +247,20 @@ class Movie(Video): if 'title' not in guess: raise ValueError('Insufficient data to process the guess') - return cls(name, guess['title'], format=guess.get('format'), release_group=guess.get('release_group'), + return cls(name, guess['title'], source=guess.get('source'), release_group=guess.get('release_group'), + streaming_service=guess.get('streaming_service'), resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'), - audio_codec=guess.get('audio_codec'), year=guess.get('year')) + alternative_titles=ensure_list(guess.get('alternative_title')), + audio_codec=guess.get('audio_codec'), year=guess.get('year'), country=guess.get('country')) @classmethod def fromname(cls, name): return cls.fromguess(name, guessit(name, {'type': 'movie'})) def __repr__(self): - if self.year is None: - return '<%s [%r]>' % (self.__class__.__name__, self.title) - - return '<%s [%r, %d]>' % (self.__class__.__name__, self.title, self.year) + return '<{cn} [{title}{open}{country}{sep}{year}{close}]>'.format( + cn=self.__class__.__name__, title=self.title, year=self.year or '', country=self.country or '', + open=' (' if self.year or self.country else '', + sep=') (' if self.year and self.country else '', + close=')' if self.year or self.country else '' + ) diff --git a/libs/common/typing_extensions.py b/libs/common/typing_extensions.py new file mode 100644 index 00000000..194731cd --- /dev/null +++ b/libs/common/typing_extensions.py @@ -0,0 +1,2908 @@ +import abc +import collections +import collections.abc +import operator +import sys +import types as _types +import typing + +# After PEP 560, internal typing API was substantially reworked. +# This is especially important for Protocol class which uses internal APIs +# quite extensively. +PEP_560 = sys.version_info[:3] >= (3, 7, 0) + +if PEP_560: + GenericMeta = type +else: + # 3.6 + from typing import GenericMeta, _type_vars # noqa + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'Self', + 'Type', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsIndex', + + # One-off things. + 'Annotated', + 'assert_never', + 'dataclass_transform', + 'final', + 'IntVar', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'Protocol', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeGuard', + 'TYPE_CHECKING', + 'Never', + 'NoReturn', + 'Required', + 'NotRequired', +] + +if PEP_560: + __all__.extend(["get_args", "get_origin", "get_type_hints"]) + +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. + + +def _no_slots_copy(dct): + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +_marker = object() + + +def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" + f" actual {alen}, expected {elen}") + + +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +elif sys.version_info >= (3, 9): + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) +else: + def _should_collect_from_parameters(t): + return isinstance(t, typing._GenericAlias) and not t._special + + +def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + for t in types: + if ( + isinstance(t, typevar_types) and + t not in tvars and + not _is_unpack(t) + ): + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + return tuple(tvars) + + +# 3.6.2+ +if hasattr(typing, 'NoReturn'): + NoReturn = typing.NoReturn +# 3.6.0-3.6.1 +else: + class _NoReturn(typing._FinalTypingBase, _root=True): + """Special type indicating functions that never return. + Example:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise Exception('no way') + + This type is invalid in other positions, e.g., ``List[NoReturn]`` + will fail in static type checkers. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("NoReturn cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("NoReturn cannot be used with issubclass().") + + NoReturn = _NoReturn(_root=True) + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +ClassVar = typing.ClassVar + +# On older versions of typing there is an internal class named "Final". +# 3.8+ +if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): + Final = typing.Final +# 3.7 +elif sys.version_info[:2] >= (3, 7): + class _FinalForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + Final = _FinalForm('Final', + doc="""A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties.""") +# 3.6 +else: + class _Final(typing._FinalTypingBase, _root=True): + """A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties. + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + f'{cls.__name__[1:]} accepts only single type.'), + _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += f'[{typing._type_repr(self.__type__)}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Final): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + Final = _Final(_root=True) + + +if sys.version_info >= (3, 11): + final = typing.final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +def IntVar(name): + return typing.TypeVar(name) + + +# 3.8+: +if hasattr(typing, 'Literal'): + Literal = typing.Literal +# 3.7: +elif sys.version_info[:2] >= (3, 7): + class _LiteralForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return typing._GenericAlias(self, parameters) + + Literal = _LiteralForm('Literal', + doc="""A type that can be used to indicate to type checkers + that the corresponding value has a value literally equivalent + to the provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to + the value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime + checking verifying that the parameter is actually a value + instead of a type.""") +# 3.6: +else: + class _Literal(typing._FinalTypingBase, _root=True): + """A type that can be used to indicate to type checkers that the + corresponding value has a value literally equivalent to the + provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to the + value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime checking + verifying that the parameter is actually a value instead of a type. + """ + + __slots__ = ('__values__',) + + def __init__(self, values=None, **kwds): + self.__values__ = values + + def __getitem__(self, values): + cls = type(self) + if self.__values__ is None: + if not isinstance(values, tuple): + values = (values,) + return cls(values, _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + return self + + def __repr__(self): + r = super().__repr__() + if self.__values__ is not None: + r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__values__)) + + def __eq__(self, other): + if not isinstance(other, _Literal): + return NotImplemented + if self.__values__ is not None: + return self.__values__ == other.__values__ + return self is other + + Literal = _Literal(_root=True) + + +_overload_dummy = typing._overload_dummy # noqa +overload = typing.overload + + +# This is not a real generic class. Don't use outside annotations. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + + +class _ExtensionsGenericMeta(GenericMeta): + def __subclasscheck__(self, subclass): + """This mimics a more modern GenericMeta.__subclasscheck__() logic + (that does not have problems with recursion) to work around interactions + between collections, typing, and typing_extensions on older + versions of Python, see https://github.com/python/typing/issues/501. + """ + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if not self.__extra__: + return super().__subclasscheck__(subclass) + res = self.__extra__.__subclasshook__(subclass) + if res is not NotImplemented: + return res + if self.__extra__ in subclass.__mro__: + return True + for scls in self.__extra__.__subclasses__(): + if isinstance(scls, GenericMeta): + continue + if issubclass(subclass, scls): + return True + return False + + +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator + +# 3.6.1+ +if hasattr(typing, 'Deque'): + Deque = typing.Deque +# 3.6.0 +else: + class Deque(collections.deque, typing.MutableSequence[T], + metaclass=_ExtensionsGenericMeta, + extra=collections.deque): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Deque: + return collections.deque(*args, **kwds) + return typing._generic_new(collections.deque, cls, *args, **kwds) + +ContextManager = typing.ContextManager +# 3.6.2+ +if hasattr(typing, 'AsyncContextManager'): + AsyncContextManager = typing.AsyncContextManager +# 3.6.0-3.6.1 +else: + from _collections_abc import _check_methods as _check_methods_in_mro # noqa + + class AsyncContextManager(typing.Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + return _check_methods_in_mro(C, "__aenter__", "__aexit__") + return NotImplemented + +DefaultDict = typing.DefaultDict + +# 3.7.2+ +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +# 3.7.0-3.7.2 +elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +# 3.6 +else: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is OrderedDict: + return collections.OrderedDict(*args, **kwds) + return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) + +# 3.6.2+ +if hasattr(typing, 'Counter'): + Counter = typing.Counter +# 3.6.0-3.6.1 +else: + class Counter(collections.Counter, + typing.Dict[T, int], + metaclass=_ExtensionsGenericMeta, extra=collections.Counter): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Counter: + return collections.Counter(*args, **kwds) + return typing._generic_new(collections.Counter, cls, *args, **kwds) + +# 3.6.1+ +if hasattr(typing, 'ChainMap'): + ChainMap = typing.ChainMap +elif hasattr(collections, 'ChainMap'): + class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.ChainMap): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return typing._generic_new(collections.ChainMap, cls, *args, **kwds) + +# 3.6.1+ +if hasattr(typing, 'AsyncGenerator'): + AsyncGenerator = typing.AsyncGenerator +# 3.6.0 +else: + class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], + metaclass=_ExtensionsGenericMeta, + extra=collections.abc.AsyncGenerator): + __slots__ = () + +NewType = typing.NewType +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +def _gorg(cls): + """This function exists for compatibility with old typing versions.""" + assert isinstance(cls, GenericMeta) + if hasattr(cls, '_gorg'): + return cls._gorg + while cls.__origin__ is not None: + cls = cls.__origin__ + return cls + + +_PROTO_WHITELIST = ['Callable', 'Awaitable', + 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', + 'ContextManager', 'AsyncContextManager'] + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in ('Protocol', 'Generic'): + continue + annotations = getattr(base, '__annotations__', {}) + for attr in list(base.__dict__.keys()) + list(annotations.keys()): + if (not attr.startswith('_abc_') and attr not in ( + '__abstractmethods__', '__annotations__', '__weakref__', + '_is_protocol', '_is_runtime_protocol', '__dict__', + '__args__', '__slots__', + '__next_in_mro__', '__parameters__', '__origin__', + '__orig_bases__', '__extra__', '__tree_hash__', + '__doc__', '__subclasshook__', '__init__', '__new__', + '__module__', '_MutableMapping__marker', '_gorg')): + attrs.add(attr) + return attrs + + +def _is_callable_members_only(cls): + return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) + + +# 3.8+ +if hasattr(typing, 'Protocol'): + Protocol = typing.Protocol +# 3.7 +elif PEP_560: + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(abc.ABCMeta): + # This metaclass is a bit unfortunate and exists only because of the lack + # of __instancehook__. + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(cls, '_is_protocol', False) or + _is_callable_members_only(cls)) and + issubclass(instance.__class__, cls)): + return True + if cls._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(cls, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(cls)): + return True + return super().__instancecheck__(instance) + + class Protocol(metaclass=_ProtocolMeta): + # There is quite a lot of overlapping code with typing.Generic. + # Unfortunately it is hard to avoid this while these live in two different + # modules. The duplicated code will be removed when Protocol is moved to typing. + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if cls is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can only be used as a base class") + return super().__new__(cls) + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple): + params = (params,) + if not params and cls is not typing.Tuple: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) # noqa + if cls is Protocol: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, typing.TypeVar) for p in params): + i = 0 + while isinstance(params[i], typing.TypeVar): + i += 1 + raise TypeError( + "Parameters to Protocol[...] must all be type variables." + f" Parameter {i + 1} is {params[i]}") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Protocol[...] must all be unique") + else: + # Subscripting a regular Generic subclass. + _check_generic(cls, params, len(cls.__parameters__)) + return typing._GenericAlias(cls, params) + + def __init_subclass__(cls, *args, **kwargs): + tvars = [] + if '__orig_bases__' in cls.__dict__: + error = typing.Generic in cls.__orig_bases__ + else: + error = typing.Generic in cls.__bases__ + if error: + raise TypeError("Cannot inherit from plain Generic") + if '__orig_bases__' in cls.__dict__: + tvars = typing._collect_type_vars(cls.__orig_bases__) + # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...] and/or Protocol[...]. + gvars = None + for base in cls.__orig_bases__: + if (isinstance(base, typing._GenericAlias) and + base.__origin__ in (typing.Generic, Protocol)): + # for error messages + the_base = base.__origin__.__name__ + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...]" + " and/or Protocol[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) + s_args = ', '.join(str(g) for g in gvars) + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {the_base}[{s_args}]") + tvars = gvars + cls.__parameters__ = tuple(tvars) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not getattr(cls, '_is_runtime_protocol', False): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if not _is_callable_members_only(cls): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # We have nothing more to do for non-protocols. + if not cls._is_protocol: + return + + # Check consistency of bases. + for base in cls.__bases__: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, _ProtocolMeta) and base._is_protocol): + raise TypeError('Protocols can only inherit from other' + f' protocols, got {repr(base)}') + cls.__init__ = _no_init +# 3.6 +else: + from typing import _next_in_mro, _type_check # noqa + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(GenericMeta): + """Internal metaclass for Protocol. + + This exists so Protocol classes can be generic without deriving + from Generic. + """ + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + # This is just a version copied from GenericMeta.__new__ that + # includes "Protocol" special treatment. (Comments removed for brevity.) + assert extra is None # Protocols should not have extra + if tvars is not None: + assert origin is not None + assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars + else: + tvars = _type_vars(bases) + gvars = None + for base in bases: + if base is typing.Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ in (typing.Generic, Protocol)): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] or" + " Protocol[...] multiple times.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) + s_args = ", ".join(str(g) for g in gvars) + cls_name = "Generic" if any(b.__origin__ is typing.Generic + for b in bases) else "Protocol" + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {cls_name}[{s_args}]") + tvars = gvars + + initial_bases = bases + if (extra is not None and type(extra) is abc.ABCMeta and + extra not in bases): + bases = (extra,) + bases + bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b + for b in bases) + if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): + bases = tuple(b for b in bases if b is not typing.Generic) + namespace.update({'__origin__': origin, '__extra__': extra}) + self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, + _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else + _gorg(origin)) + self.__parameters__ = tvars + self.__args__ = tuple(... if a is typing._TypingEllipsis else + () if a is typing._TypingEmpty else + a for a in args) if args else None + self.__next_in_mro__ = _next_in_mro(self) + if orig_bases is None: + self.__orig_bases__ = initial_bases + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + if hasattr(self, '_subs_tree'): + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + def __init__(cls, *args, **kwargs): + super().__init__(*args, **kwargs) + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol or + isinstance(b, _ProtocolMeta) and + b.__origin__ is Protocol + for b in cls.__bases__) + if cls._is_protocol: + for base in cls.__mro__[1:]: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, typing.TypingMeta) and base._is_protocol or + isinstance(base, GenericMeta) and + base.__origin__ is typing.Generic): + raise TypeError(f'Protocols can only inherit from other' + f' protocols, got {repr(base)}') + + cls.__init__ = _no_init + + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + def __instancecheck__(self, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(self, '_is_protocol', False) or + _is_callable_members_only(self)) and + issubclass(instance.__class__, self)): + return True + if self._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(self, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(self)): + return True + return super(GenericMeta, self).__instancecheck__(instance) + + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if (self.__dict__.get('_is_protocol', None) and + not self.__dict__.get('_is_runtime_protocol', None)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return False + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if (self.__dict__.get('_is_runtime_protocol', None) and + not _is_callable_members_only(self)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return super(GenericMeta, self).__subclasscheck__(cls) + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + return super(GenericMeta, self).__subclasscheck__(cls) + + @typing._tp_cache + def __getitem__(self, params): + # We also need to copy this from GenericMeta.__getitem__ to get + # special treatment of "Protocol". (Comments removed for brevity.) + if not isinstance(params, tuple): + params = (params,) + if not params and _gorg(self) is not typing.Tuple: + raise TypeError( + f"Parameter list to {self.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self in (typing.Generic, Protocol): + if not all(isinstance(p, typing.TypeVar) for p in params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be unique") + tvars = params + args = params + elif self in (typing.Tuple, typing.Callable): + tvars = _type_vars(params) + args = params + elif self.__origin__ in (typing.Generic, Protocol): + raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") + else: + _check_generic(self, params, len(self.__parameters__)) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + class Protocol(metaclass=_ProtocolMeta): + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if _gorg(cls) is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can be used only as a base class") + return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +# 3.8+ +if hasattr(typing, 'runtime_checkable'): + runtime_checkable = typing.runtime_checkable +# 3.6-3.7 +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol, so that it + can be used with isinstance() and issubclass(). Raise TypeError + if applied to a non-protocol class. + + This allows a simple-minded structural check very similar to the + one-offs in collections.abc such as Hashable. + """ + if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + return cls + + +# Exists for backwards compatibility. +runtime = runtime_checkable + + +# 3.8+ +if hasattr(typing, 'SupportsIndex'): + SupportsIndex = typing.SupportsIndex +# 3.6-3.7 +else: + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + +if hasattr(typing, "Required"): + # The standard library TypedDict in Python 3.8 does not store runtime information + # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', + 'functools', + 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False + + def _dict_new(*args, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + return dict(*args, **kwargs) + + _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)' + + def _typeddict_new(*args, total=True, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + if args: + typename, args = args[0], args[1:] # allow the "_typename" keyword be passed + elif '_typename' in kwargs: + typename = kwargs.pop('_typename') + import warnings + warnings.warn("Passing '_typename' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + raise TypeError("TypedDict.__new__() missing 1 required positional " + "argument: '_typename'") + if args: + try: + fields, = args # allow the "_fields" keyword be passed + except ValueError: + raise TypeError('TypedDict.__new__() takes from 2 to 3 ' + f'positional arguments but {len(args) + 2} ' + 'were given') + elif '_fields' in kwargs and len(kwargs) == 1: + fields = kwargs.pop('_fields') + import warnings + warnings.warn("Passing '_fields' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + fields = None + + if fields is None: + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + + ns = {'__annotations__': dict(fields)} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + + return _TypedDictMeta(typename, (), ns, total=total) + + _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' + ' /, *, total=True, **kwargs)') + + class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + super().__init__(name, bases, ns) + + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new + tp_dict = super().__new__(cls, name, (dict,), ns) + + annotations = {} + own_annotations = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + own_annotations = { + n: typing._type_check(tp, msg) for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + + for base in bases: + annotations.update(base.__dict__.get('__annotations__', {})) + required_keys.update(base.__dict__.get('__required_keys__', ())) + optional_keys.update(base.__dict__.get('__optional_keys__', ())) + + annotations.update(own_annotations) + if PEP_560: + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) + else: + own_annotation_keys = set(own_annotations.keys()) + if total: + required_keys.update(own_annotation_keys) + else: + optional_keys.update(own_annotation_keys) + + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __instancecheck__ = __subclasscheck__ = _check_fails + + TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) + TypedDict.__module__ = __name__ + TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports two additional equivalent forms:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + The class syntax is only supported in Python 3.6+, while two other + syntax forms work for Python 2.7 and 3.2+ + """ + + if hasattr(typing, "_TypedDictMeta"): + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + else: + _TYPEDDICT_TYPES = (_TypedDictMeta,) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + +if hasattr(typing, "Required"): + get_type_hints = typing.get_type_hints +elif PEP_560: + import functools + import types + + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, _AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return types.GenericAlias(t.__origin__, stripped_args) + if hasattr(types, "UnionType") and isinstance(t, types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if hasattr(typing, "Annotated"): + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + else: + hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + +# Python 3.9+ has PEP 593 (Annotated) +if hasattr(typing, 'Annotated'): + Annotated = typing.Annotated + # Not exported and not a public API, but needed for get_origin() and get_args() + # to work. + _AnnotatedAlias = typing._AnnotatedAlias +# 3.7-3.8 +elif PEP_560: + class _AnnotatedAlias(typing._GenericAlias, _root=True): + """Runtime representation of an annotated type. + + At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't' + with extra annotations. The alias behaves like a normal typing alias, + instantiating is the same as instantiating the underlying type, binding + it to types is also the same. + """ + def __init__(self, origin, metadata): + if isinstance(origin, _AnnotatedAlias): + metadata = origin.__metadata__ + metadata + origin = origin.__origin__ + super().__init__(origin, origin) + self.__metadata__ = metadata + + def copy_with(self, params): + assert len(params) == 1 + new_type = params[0] + return _AnnotatedAlias(new_type, self.__metadata__) + + def __repr__(self): + return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " + f"{', '.join(repr(a) for a in self.__metadata__)}]") + + def __reduce__(self): + return operator.getitem, ( + Annotated, (self.__origin__,) + self.__metadata__ + ) + + def __eq__(self, other): + if not isinstance(other, _AnnotatedAlias): + return NotImplemented + if self.__origin__ != other.__origin__: + return False + return self.__metadata__ == other.__metadata__ + + def __hash__(self): + return hash((self.__origin__, self.__metadata__)) + + class Annotated: + """Add context specific metadata to a type. + + Example: Annotated[int, runtime_check.Unsigned] indicates to the + hypothetical runtime_check module that this type is an unsigned int. + Every other consumer of this type can ignore this metadata and treat + this type as int. + + The first argument to Annotated must be a valid type (and will be in + the __origin__ field), the remaining arguments are kept as a tuple in + the __extra__ field. + + Details: + + - It's an error to call `Annotated` with less than two arguments. + - Nested Annotated are flattened:: + + Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] + + - Instantiating an annotated type is equivalent to instantiating the + underlying type:: + + Annotated[C, Ann1](5) == C(5) + + - Annotated can be used as a generic type alias:: + + Optimized = Annotated[T, runtime.Optimize()] + Optimized[int] == Annotated[int, runtime.Optimize()] + + OptimizedList = Annotated[List[T], runtime.Optimize()] + OptimizedList[int] == Annotated[List[int], runtime.Optimize()] + """ + + __slots__ = () + + def __new__(cls, *args, **kwargs): + raise TypeError("Type Annotated cannot be instantiated.") + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be used " + "with at least two arguments (a type and an " + "annotation).") + allowed_special_forms = (ClassVar, Final) + if get_origin(params[0]) in allowed_special_forms: + origin = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + origin = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return _AnnotatedAlias(origin, metadata) + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + f"Cannot subclass {cls.__module__}.Annotated" + ) +# 3.6 +else: + + def _is_dunder(name): + """Returns True if name is a __dunder_variable_name__.""" + return len(name) > 4 and name.startswith('__') and name.endswith('__') + + # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality + # checks, argument expansion etc. are done on the _subs_tre. As a result we + # can't provide a get_type_hints function that strips out annotations. + + class AnnotatedMeta(typing.GenericMeta): + """Metaclass for Annotated""" + + def __new__(cls, name, bases, namespace, **kwargs): + if any(b is not object for b in bases): + raise TypeError("Cannot subclass " + str(Annotated)) + return super().__new__(cls, name, bases, namespace, **kwargs) + + @property + def __metadata__(self): + return self._subs_tree()[2] + + def _tree_repr(self, tree): + cls, origin, metadata = tree + if not isinstance(origin, tuple): + tp_repr = typing._type_repr(origin) + else: + tp_repr = origin[0]._tree_repr(origin) + metadata_reprs = ", ".join(repr(arg) for arg in metadata) + return f'{cls}[{tp_repr}, {metadata_reprs}]' + + def _subs_tree(self, tvars=None, args=None): # noqa + if self is Annotated: + return Annotated + res = super()._subs_tree(tvars=tvars, args=args) + # Flatten nested Annotated + if isinstance(res[1], tuple) and res[1][0] is Annotated: + sub_tp = res[1][1] + sub_annot = res[1][2] + return (Annotated, sub_tp, sub_annot + res[2]) + return res + + def _get_cons(self): + """Return the class used to create instance of this type.""" + if self.__origin__ is None: + raise TypeError("Cannot get the underlying type of a " + "non-specialized Annotated type.") + tree = self._subs_tree() + while isinstance(tree, tuple) and tree[0] is Annotated: + tree = tree[1] + if isinstance(tree, tuple): + return tree[0] + else: + return tree + + @typing._tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if self.__origin__ is not None: # specializing an instantiated type + return super().__getitem__(params) + elif not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be instantiated " + "with at least two arguments (a type and an " + "annotation).") + else: + if ( + isinstance(params[0], typing._TypingBase) and + type(params[0]).__name__ == "_ClassVar" + ): + tp = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + tp = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return self.__class__( + self.__name__, + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=_type_vars((tp,)), + # Metadata is a tuple so it won't be touched by _replace_args et al. + args=(tp, metadata), + origin=self, + ) + + def __call__(self, *args, **kwargs): + cons = self._get_cons() + result = cons(*args, **kwargs) + try: + result.__orig_class__ = self + except AttributeError: + pass + return result + + def __getattr__(self, attr): + # For simplicity we just don't relay all dunder names + if self.__origin__ is not None and not _is_dunder(attr): + return getattr(self._get_cons(), attr) + raise AttributeError(attr) + + def __setattr__(self, attr, value): + if _is_dunder(attr) or attr.startswith('_abc_'): + super().__setattr__(attr, value) + elif self.__origin__ is None: + raise AttributeError(attr) + else: + setattr(self._get_cons(), attr, value) + + def __instancecheck__(self, obj): + raise TypeError("Annotated cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Annotated cannot be used with issubclass().") + + class Annotated(metaclass=AnnotatedMeta): + """Add context specific metadata to a type. + + Example: Annotated[int, runtime_check.Unsigned] indicates to the + hypothetical runtime_check module that this type is an unsigned int. + Every other consumer of this type can ignore this metadata and treat + this type as int. + + The first argument to Annotated must be a valid type, the remaining + arguments are kept as a tuple in the __metadata__ field. + + Details: + + - It's an error to call `Annotated` with less than two arguments. + - Nested Annotated are flattened:: + + Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] + + - Instantiating an annotated type is equivalent to instantiating the + underlying type:: + + Annotated[C, Ann1](5) == C(5) + + - Annotated can be used as a generic type alias:: + + Optimized = Annotated[T, runtime.Optimize()] + Optimized[int] == Annotated[int, runtime.Optimize()] + + OptimizedList = Annotated[List[T], runtime.Optimize()] + OptimizedList[int] == Annotated[List[int], runtime.Optimize()] + """ + +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +elif PEP_560: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias + + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, _AnnotatedAlias): + return (tp.__origin__,) + tp.__metadata__ + if isinstance(tp, (typing._GenericAlias, GenericAlias)): + if getattr(tp, "_special", False): + return () + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeAliasForm + def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") +# 3.7-3.8 +elif sys.version_info[:2] >= (3, 7): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + TypeAlias = _TypeAliasForm('TypeAlias', + doc="""Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example + above.""") +# 3.6 +else: + class _TypeAliasMeta(typing.TypingMeta): + """Metaclass for TypeAlias""" + + def __repr__(self): + return 'typing_extensions.TypeAlias' + + class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("TypeAlias cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeAlias cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.TypeAlias' + + TypeAlias = _TypeAliasBase(_root=True) + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.6-3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + +# 3.10+ +if hasattr(typing, 'ParamSpec'): + ParamSpec = typing.ParamSpec +# 3.6-3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + if not PEP_560: + # Only needed in 3.6. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + +# 3.6-3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + if PEP_560: + __class__ = typing._GenericAlias + else: + __class__ = typing._TypingBase + + # Flag in 3.8. + _special = False + # Attribute in 3.6 and earlier. + _gorg = typing.Generic + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + if not PEP_560: + # Only required in 3.6. + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + typing._get_type_vars(self.__parameters__, tvars) + + +# 3.6-3.9 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +# 3.10+ +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) +# 3.7-8 +elif sys.version_info[:2] >= (3, 7): + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm( + 'Concatenate', + doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """) +# 3.6 +else: + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __repr__(self): + return 'typing_extensions.Concatenate' + + class _ConcatenateAliasBase(typing._FinalTypingBase, + metaclass=_ConcatenateAliasMeta, + _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Concatenate cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Concatenate cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.Concatenate' + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateAliasBase(_root=True) + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, f'{self} accepts only single type.') + return typing._GenericAlias(self, (item,)) +# 3.7-3.8 +elif sys.version_info[:2] >= (3, 7): + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """) +# 3.6 +else: + class _TypeGuard(typing._FinalTypingBase, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + f'{cls.__name__[1:]} accepts only a single type.'), + _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += f'[{typing._type_repr(self.__type__)}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _TypeGuard): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + TypeGuard = _TypeGuard(_root=True) + + +if sys.version_info[:2] >= (3, 7): + # Vendored from cpython typing._SpecialFrom + class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + +if hasattr(typing, "LiteralString"): + LiteralString = typing.LiteralString +elif sys.version_info[:2] >= (3, 7): + @_SpecialForm + def LiteralString(self, params): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + raise TypeError(f"{self} is not subscriptable") +else: + class _LiteralString(typing._FinalTypingBase, _root=True): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + LiteralString = _LiteralString(_root=True) + + +if hasattr(typing, "Self"): + Self = typing.Self +elif sys.version_info[:2] >= (3, 7): + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") +else: + class _Self(typing._FinalTypingBase, _root=True): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Self = _Self(_root=True) + + +if hasattr(typing, "Never"): + Never = typing.Never +elif sys.version_info[:2] >= (3, 7): + @_SpecialForm + def Never(self, params): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + raise TypeError(f"{self} is not subscriptable") +else: + class _Never(typing._FinalTypingBase, _root=True): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Never = _Never(_root=True) + + +if hasattr(typing, 'Required'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + +elif sys.version_info[:2] >= (3, 7): + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) + return typing._GenericAlias(self, (item,)) + + Required = _RequiredForm( + 'Required', + doc="""A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """) + NotRequired = _RequiredForm( + 'NotRequired', + doc="""A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """) +else: + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _MaybeRequired(typing._FinalTypingBase, _root=True): + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, type(self)): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class _Required(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + + class _NotRequired(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + + Required = _Required(_root=True) + NotRequired = _NotRequired(_root=True) + + +if sys.version_info[:2] >= (3, 9): + class _UnpackSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + @_UnpackSpecialForm + def Unpack(self, parameters): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +elif sys.version_info[:2] >= (3, 7): + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + class _UnpackForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + Unpack = _UnpackForm( + 'Unpack', + doc="""A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +else: + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _Unpack(typing._FinalTypingBase, _root=True): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + __slots__ = ('__type__',) + __class__ = typing.TypeVar + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + 'Unpack accepts only single type.'), + _root=True) + raise TypeError('Unpack cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Unpack): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + # For 3.6 only + def _get_type_vars(self, tvars): + self.__type__._get_type_vars(tvars) + + Unpack = _Unpack(_root=True) + + def _is_unpack(obj): + return isinstance(obj, _Unpack) + + +class TypeVarTuple: + """Type variable tuple. + + Usage:: + + Ts = TypeVarTuple('Ts') + + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as + ``Tuple[int, str]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + class Array(Generic[*Ts]): ... + + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. + + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: + + class Array(Generic[*Ts]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape + + def get_shape(self) -> Tuple[*Ts]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name): + self.__name__ = name + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + return self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + if not PEP_560: + # Only needed in 3.6. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + +if hasattr(typing, "reveal_type"): + reveal_type = typing.reveal_type +else: + def reveal_type(__obj: T) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr) + return __obj + + +if hasattr(typing, "assert_never"): + assert_never = typing.assert_never +else: + def assert_never(__arg: Never) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + raise AssertionError("Expected code to be unreachable") + + +if hasattr(typing, 'dataclass_transform'): + dataclass_transform = typing.dataclass_transform +else: + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], + ... + ] = (), + ) -> typing.Callable[[T], T]: + """Decorator that marks a function, class, or metaclass as providing + dataclass-like behavior. + + Example: + + from typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``field_descriptors`` specifies a static list of supported classes + or functions, that describe fields, similar to ``dataclasses.field()``. + + At runtime, this decorator records its arguments in the + ``__dataclass_transform__`` attribute on the decorated object. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "field_descriptors": field_descriptors, + } + return cls_or_fn + return decorator + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic diff --git a/libs/common/zipp/__init__.py b/libs/common/zipp/__init__.py new file mode 100644 index 00000000..ad01e27e --- /dev/null +++ b/libs/common/zipp/__init__.py @@ -0,0 +1,381 @@ +import io +import posixpath +import zipfile +import itertools +import contextlib +import pathlib +import re +import fnmatch + +from .py310compat import text_encoding + + +__all__ = ['Path'] + + +def _parents(path): + """ + Given a path with elements separated by + posixpath.sep, generate all parents of that path. + + >>> list(_parents('b/d')) + ['b'] + >>> list(_parents('/b/d/')) + ['/b'] + >>> list(_parents('b/d/f/')) + ['b/d', 'b'] + >>> list(_parents('b')) + [] + >>> list(_parents('')) + [] + """ + return itertools.islice(_ancestry(path), 1, None) + + +def _ancestry(path): + """ + Given a path with elements separated by + posixpath.sep, generate all elements of that path + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] + >>> list(_ancestry('/b/d/')) + ['/b/d', '/b'] + >>> list(_ancestry('b/d/f/')) + ['b/d/f', 'b/d', 'b'] + >>> list(_ancestry('b')) + ['b'] + >>> list(_ancestry('')) + [] + """ + path = path.rstrip(posixpath.sep) + while path and path != posixpath.sep: + yield path + path, tail = posixpath.split(path) + + +_dedupe = dict.fromkeys +"""Deduplicate an iterable in original order""" + + +def _difference(minuend, subtrahend): + """ + Return items in minuend not in subtrahend, retaining order + with O(1) lookup. + """ + return itertools.filterfalse(set(subtrahend).__contains__, minuend) + + +class InitializedState: + """ + Mix-in to save the initialization state for pickling. + """ + + def __init__(self, *args, **kwargs): + self.__args = args + self.__kwargs = kwargs + super().__init__(*args, **kwargs) + + def __getstate__(self): + return self.__args, self.__kwargs + + def __setstate__(self, state): + args, kwargs = state + super().__init__(*args, **kwargs) + + +class CompleteDirs(InitializedState, zipfile.ZipFile): + """ + A ZipFile subclass that ensures that implied directories + are always included in the namelist. + """ + + @staticmethod + def _implied_dirs(names): + parents = itertools.chain.from_iterable(map(_parents, names)) + as_dirs = (p + posixpath.sep for p in parents) + return _dedupe(_difference(as_dirs, names)) + + def namelist(self): + names = super(CompleteDirs, self).namelist() + return names + list(self._implied_dirs(names)) + + def _name_set(self): + return set(self.namelist()) + + def resolve_dir(self, name): + """ + If the name represents a directory, return that name + as a directory (with the trailing slash). + """ + names = self._name_set() + dirname = name + '/' + dir_match = name not in names and dirname in names + return dirname if dir_match else name + + @classmethod + def make(cls, source): + """ + Given a source (filename or zipfile), return an + appropriate CompleteDirs subclass. + """ + if isinstance(source, CompleteDirs): + return source + + if not isinstance(source, zipfile.ZipFile): + return cls(source) + + # Only allow for FastLookup when supplied zipfile is read-only + if 'r' not in source.mode: + cls = CompleteDirs + + source.__class__ = cls + return source + + +class FastLookup(CompleteDirs): + """ + ZipFile subclass to ensure implicit + dirs exist and are resolved rapidly. + """ + + def namelist(self): + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super(FastLookup, self).namelist() + return self.__names + + def _name_set(self): + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super(FastLookup, self)._name_set() + return self.__lookup + + +class Path: + """ + A pathlib-compatible interface for zip files. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = zipfile.ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'mem/abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> root = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = root.iterdir() + >>> a + Path('mem/abcde.zip', 'a.txt') + >>> b + Path('mem/abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('mem/abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text() + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coercion to string: + + >>> import os + >>> str(c).replace(os.sep, posixpath.sep) + 'mem/abcde.zip/b/c.txt' + + At the root, ``name``, ``filename``, and ``parent`` + resolve to the zipfile. Note these attributes are not + valid and will raise a ``ValueError`` if the zipfile + has no filename. + + >>> root.name + 'abcde.zip' + >>> str(root.filename).replace(os.sep, posixpath.sep) + 'mem/abcde.zip' + >>> str(root.parent) + 'mem' + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + """ + Construct a Path from a ZipFile or filename. + + Note: When the source is an existing ZipFile object, + its type (__class__) will be mutated to a + specialized type. If the caller wishes to retain the + original type, the caller should either create a + separate ZipFile object or pass a filename. + """ + self.root = FastLookup.make(root) + self.at = at + + def __eq__(self, other): + """ + >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo' + False + """ + if self.__class__ is not other.__class__: + return NotImplemented + return (self.root, self.at) == (other.root, other.at) + + def __hash__(self): + return hash((self.root, self.at)) + + def open(self, mode='r', *args, pwd=None, **kwargs): + """ + Open this entry as text or binary following the semantics + of ``pathlib.Path.open()`` by passing arguments through + to io.TextIOWrapper(). + """ + if self.is_dir(): + raise IsADirectoryError(self) + zip_mode = mode[0] + if not self.exists() and zip_mode == 'r': + raise FileNotFoundError(self) + stream = self.root.open(self.at, zip_mode, pwd=pwd) + if 'b' in mode: + if args or kwargs: + raise ValueError("encoding args invalid for binary operation") + return stream + else: + kwargs["encoding"] = text_encoding(kwargs.get("encoding")) + return io.TextIOWrapper(stream, *args, **kwargs) + + @property + def name(self): + return pathlib.Path(self.at).name or self.filename.name + + @property + def suffix(self): + return pathlib.Path(self.at).suffix or self.filename.suffix + + @property + def suffixes(self): + return pathlib.Path(self.at).suffixes or self.filename.suffixes + + @property + def stem(self): + return pathlib.Path(self.at).stem or self.filename.stem + + @property + def filename(self): + return pathlib.Path(self.root.filename).joinpath(self.at) + + def read_text(self, *args, **kwargs): + kwargs["encoding"] = text_encoding(kwargs.get("encoding")) + with self.open('r', *args, **kwargs) as strm: + return strm.read() + + def read_bytes(self): + with self.open('rb') as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return self.__class__(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return self.exists() and not self.is_dir() + + def exists(self): + return self.at in self.root._name_set() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self.root.namelist()) + return filter(self._is_child, subs) + + def match(self, path_pattern): + return pathlib.Path(self.at).match(path_pattern) + + def is_symlink(self): + """ + Return whether this path is a symlink. Always false (python/cpython#82102). + """ + return False + + def _descendants(self): + for child in self.iterdir(): + yield child + if child.is_dir(): + yield from child._descendants() + + def glob(self, pattern): + if not pattern: + raise ValueError("Unacceptable pattern: {!r}".format(pattern)) + + matches = re.compile(fnmatch.translate(pattern)).fullmatch + return ( + child + for child in self._descendants() + if matches(str(child.relative_to(self))) + ) + + def rglob(self, pattern): + return self.glob(f'**/{pattern}') + + def relative_to(self, other, *extra): + return posixpath.relpath(str(self), str(other.joinpath(*extra))) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def joinpath(self, *other): + next = posixpath.join(self.at, *other) + return self._next(self.root.resolve_dir(next)) + + __truediv__ = joinpath + + @property + def parent(self): + if not self.at: + return self.filename.parent + parent_at = posixpath.dirname(self.at.rstrip('/')) + if parent_at: + parent_at += '/' + return self._next(parent_at) diff --git a/libs/common/zipp/py310compat.py b/libs/common/zipp/py310compat.py new file mode 100644 index 00000000..8244124c --- /dev/null +++ b/libs/common/zipp/py310compat.py @@ -0,0 +1,12 @@ +import sys +import io + + +te_impl = 'lambda encoding, stacklevel=2, /: encoding' +te_impl_37 = te_impl.replace(', /', '') +_text_encoding = eval(te_impl) if sys.version_info > (3, 8) else eval(te_impl_37) + + +text_encoding = ( + io.text_encoding if sys.version_info > (3, 10) else _text_encoding # type: ignore +)