Compare commits

..

747 Commits
0.3.0 ... dev

Author SHA1 Message Date
f1863cc40f change ci 2025-03-15 16:54:31 +09:00
5f4ab448cb Update .github/workflows/ai-review.yml 2025-03-15 16:53:22 +09:00
8a646957eb Update .github/workflows/ai-review.yml 2025-03-15 16:53:22 +09:00
9aa703e07d Update .github/workflows/ai-review.yml 2025-03-15 16:53:22 +09:00
287cfea18a Add ci 2025-03-15 16:53:22 +09:00
Jeong, YunWon
bd94d8d50c Remove uu.py and test_uu.py (#5607)
* Remove uu.py and test_uu.py

* patch email.message
2025-03-14 11:42:26 +09:00
Ashwin Naren
7fab64ed9c Revert "Update statistics to 3.13.2 (#5592)" (#5606)
This reverts commit ff970b0e1c.
2025-03-14 11:41:14 +09:00
Ashwin Naren
8e22c399df partially fix sys.getwindowsversion() (#5595) 2025-03-14 11:38:35 +09:00
Ashwin Naren
7546ea91a9 patch email.message 2025-03-13 10:16:51 -07:00
Ashwin Naren
8da66978bf Remove uu.py and test_uu.py 2025-03-13 09:49:10 -07:00
Ashwin Naren
8484bfa2e0 Remove cgi module (#5597)
* no cgi

* mark failing test
2025-03-12 09:47:34 +09:00
Ashwin Naren
ff970b0e1c Update statistics to 3.13.2 (#5592)
* statistics to 3.13.2

* set flaky test
2025-03-11 22:37:26 +09:00
Jeong, YunWon
8be7e4327d Merge pull request #5596 from arihant2math/osx-support-313
_osx_support update to 3.13.2
2025-03-11 15:58:19 +09:00
Jeong, YunWon
82eeb237dc Merge pull request #5598 from arihant2math/fix-whats-left-again
Fixed whats left
2025-03-11 15:57:57 +09:00
Ashwin Naren
cbbadf562f Fixed whats left 2025-03-10 23:27:05 -07:00
Ashwin Naren
4308321f39 _osx_support update to 3.13 2025-03-10 23:13:32 -07:00
Jeong, YunWon
985eebf9b0 Merge pull request #5589 from youknowone/pyattr-const
Replace pyattr(once) to constant
2025-03-11 10:12:50 +09:00
Ashwin Naren
bf28152a32 add os support modules 2025-03-10 11:43:53 +09:00
Ashwin Naren
bae0ad3aeb Fix extra newline in module.csv generation in cron ci (#5591) 2025-03-10 11:43:26 +09:00
Jeong YunWon
87fae150da Replace pyattr(once) to constant 2025-03-09 12:39:12 +09:00
Ashwin Naren
97853bf0f1 Fix module.csv generation in cron ci (#5586) 2025-03-06 13:20:04 +09:00
Noa
cc0a1ce9e2 Update webpack (#5585)
* Update webpack

* Build demo before notebook

* Use with instead of env for actions-gh-pages
2025-03-05 16:15:14 -06:00
Daniel O'Hear
58ebf04bac Add JIT compilation support for integer multiplication, division, and exponents (#5561)
* Initial commit for power function

* Float power jit developed

* Addded support for Floats and Ints

* Integration Testing for JITPower implementation.

* Update instructions.rs

cranelift more like painlift

* Update instructions.rs

* Update instructions.rs

* initial commit for making stable PR ready features

* fixed final edge case for compile_ipow

* fixed final edge case for compile_ipow

* commenting out compile_ipow

* fixed spelling errors

* removed unused tests

* forgot to run clippy

---------

Co-authored-by: Nicholas Paulick <paulicknicholas@gmail.com>
Co-authored-by: Nick <nick@Samanthas-MBP.wi.rr.com>
Co-authored-by: JoeLoparco <loparcojoseph@gmail.com>
Co-authored-by: Nathan Rusch <nathan.rusch@icloud.com>
Co-authored-by: dohear <daniel.ohear@marquette.edu>
2025-03-05 14:41:45 -06:00
Ashwin Naren
7fea1e1b4a fix what is left data upload to website and trigger cron-ci on workflow update 2025-03-05 13:50:52 -06:00
Ashwin Naren
d2bf31724f fix clippy 2025-03-05 13:49:37 -06:00
Ashwin Naren
b4929d258d formatting 2025-03-05 13:49:37 -06:00
Ashwin Naren
ddf2e591c6 resolve comments 2025-03-05 13:49:37 -06:00
Ashwin Naren
33940726a8 upgrade to windows-sys 0.59.0 2025-03-05 13:49:37 -06:00
Ashwin Naren
05cb8c0b73 Update socket.rs
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2025-03-05 13:49:37 -06:00
Ashwin Naren
8d2c6807d2 fix non-windows build 2025-03-05 13:49:37 -06:00
Ashwin Naren
4d9804f188 formatting 2025-03-05 13:49:37 -06:00
Ashwin Naren
3ae1160868 Remove winapi dependency 2025-03-05 13:49:37 -06:00
Ashwin Naren
6804dd4363 use rust-toolchain targets options instead of using rustup 2025-03-05 13:45:31 -06:00
Ashwin Naren
defcadafbb publish demo on weekly release 2025-03-05 13:45:31 -06:00
Ashwin Naren
40e3f49ab7 _ctypes pt. 3 (#5530)
* Initial CFuncPtr implementation

* function calling via libffi

* working no-arg function call

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-03-03 15:53:04 +09:00
Ashwin Naren
56196890f5 Actions caching for nodejs (#5575)
* caching for nodejs and various CI dependency updates

* commit the package-lock.json
2025-03-02 18:18:17 +09:00
Noa
6daee1b00e Warn on elided_lifetimes_in_paths 2025-03-01 13:49:33 +09:00
Ashwin Naren
8ff856d7ce _ctypes addressof and Structure (#5573)
* _ctypes.addressof

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>

* ctypes.Structure implementation

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>

* clippy fix

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>

* formatting

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>

---------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-03-01 13:48:28 +09:00
Ashwin Naren
6731c4b1ab fix devcontainer.json 2025-02-28 13:50:10 -06:00
Ashwin Naren
544182ebfc update license dates 2025-02-27 19:10:56 -06:00
Noa
b2abb1af84 Remove redundant lints now that we're on edition2024 2025-02-26 23:46:57 -06:00
Jeong, YunWon
1a2dda502a Merge pull request #5560 from arihant2math/2024-migrate
Migrate to the 2024 edition
2025-02-27 12:22:03 +09:00
Ashwin Naren
b870b0e1b5 2024 edition formatting
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-26 11:48:22 -08:00
Ashwin Naren
df2354fdb7 migrate to the 2024 edition
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-26 11:47:32 -08:00
Jeong, YunWon
c20c90fbc4 Merge pull request #5563 from coolreader18/fix-zlib-tests
Fix a bunch of zlib tests & update gzip.py to Python 3.13
2025-02-26 15:47:52 +09:00
CPython Developers
aba3d5c082 Update gzip,test_gzip from CPython 3.13 2025-02-26 00:10:24 -06:00
Noa
8c5602f2fb Fix a bunch of zlib tests 2025-02-26 00:10:24 -06:00
Noa
4468dcbe34 Switch to libz-rs-sys for zlib implementation 2025-02-25 23:19:19 -06:00
Ashwin Naren
235adafa0b tests
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-25 17:19:53 +09:00
Noa
864e8598f8 Enable rust2024-incompatible pat and keyword-ident lints 2025-02-25 00:32:02 -06:00
Jeong, YunWon
f426348a94 Merge pull request #5558 from coolreader18/openssl-probe-no-env
Use non-env-var methods from openssl_probe
2025-02-25 14:54:19 +09:00
Noa
085ac88438 Use non-env-var methods from openssl_probe 2025-02-24 23:10:07 -06:00
Noa
4881f61be6 Mark env::{set,remove}_var() unsafe 2025-02-24 23:02:54 -06:00
Jeong, YunWon
ff9947ff14 Enable unsafe_op_in_unsafe_fn and missing_unsafe_on_extern lints (#5557)
* Enable unsafe_op_in_unsafe_fn lint

* Enable missing_unsafe_on_extern lint

* Make PyObjectRef::{from,into}_raw() use NonNull
2025-02-25 13:42:25 +09:00
Noa
92e02a7f79 Make PyObjectRef::{from,into}_raw() use NonNull 2025-02-24 21:25:23 -06:00
Noa
0a8b0406f5 Enable missing_unsafe_on_extern lint 2025-02-24 21:25:23 -06:00
Noa
1c3b198a17 Enable unsafe_op_in_unsafe_fn lint 2025-02-24 21:25:23 -06:00
Noa
52208b3c90 Update to syn2 (#5556) 2025-02-25 11:54:13 +09:00
Noa
2721f2de3f Fix a bunch of random tests (#5533) 2025-02-25 08:41:54 +09:00
Ashwin Naren
b55a55afc7 update the csv with the temp data for website what's left
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-24 11:30:27 -06:00
Ashwin Naren
d7a72b5755 add constants and implement functions
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-24 16:16:34 +09:00
Ashwin Naren
1f3a9672c3 Add _winapi.GetACP and enable test_unicode on windows (#5547)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-24 13:21:02 +09:00
Axect
31c5c3eb9d Update puruspe version to 0.4.0
To resolve the issue (#5496)
2025-02-24 11:15:33 +09:00
Ashwin Naren
7fada8b97e fix _ctypes error names
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 16:07:52 +09:00
Ashwin Naren
429754fd33 Fix unicode decode bug on surrogate error mode (#5546)
* subtract with overflow to check for whether to use surrogate

* enable test_argparse for windows on ci

------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 16:07:22 +09:00
Ashwin Naren
b4f0a589ed platform-dependent Windows testing (#5536)
* disable test_argparse on windows

* fix test_exceptions and mark it as platform dependent

* test importlib on windows

* explain why windows tests fail

* mark test_argparse as non platform-independent

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-23 09:48:02 +09:00
Noa
331a3c108f Switch to criterion in sre_engine benchmarks 2025-02-23 09:44:57 +09:00
Ashwin Naren
d698b28ce5 Ensure pymethod cannot be both magic and named simultaneously + macro documentation (#5538)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-22 17:22:31 +09:00
Ashwin Naren
23236aa8c7 test_datetime now works on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-21 15:09:40 +09:00
Noa
a9331bb34d Fix warnings for rust 1.85 2025-02-20 14:58:59 -06:00
Hanif Ariffin
65dcf1ce1c Updating test_math.py to CPython 3.12.9 (#5507)
* Fixed implementation against CPython 3.12.9 Lib/test/test_math.py tests
---------

Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2025-02-20 11:21:12 +09:00
Ashwin Naren
e2b0fe4266 _ctypes pt. 2 (#5524)
* add __version__

* add more types/constants

* shared library and ExternalLibs implementation

* FreeLibrary for windows

* fixed PyCSimple

* LoadLibrary and FreeLibrary for non-windows

* fault-tolerant float equality

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-20 10:50:10 +09:00
Noa
fa2acd7cde Update rand to 0.9 2025-02-18 17:07:26 +09:00
Ashwin Naren
a71c16f8cb test colorize on ci
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 16:52:04 +09:00
Ashwin Naren
f466971312 clippy
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
69b1a9910f formatting
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
4ed735b424 time.daylight for windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
175afd97d8 time.timezone for windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
72338d578b tzname on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
9856d94f2d function to retrieve tz info on windows
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-18 15:50:27 +09:00
Ashwin Naren
517ffed401 fix clippy lint
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
38a6a8d984 duplicate windows-sys
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
630c1ff8d1 simple part of the bump
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 14:15:57 -06:00
Ashwin Naren
7e1568a1ff Revert "windows-rs upgrade to 0.59"
This reverts commit 547530724e77a592734d8cd396115c4124d7a9f9.
2025-02-17 14:15:57 -06:00
Ashwin Naren
6788010f7d windows-rs upgrade to 0.59 2025-02-17 14:15:57 -06:00
Ashwin Naren
9e310934d3 fix panic
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-17 12:00:39 -06:00
Ashwin Naren
e8a3406624 itertools upgrade 2025-02-16 10:20:56 +09:00
Ashwin Naren
fde87a340c Initial _ctypes implementation (#5519)
* initial _ctypes implementation with _CData, get_errno, and set_errno

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-15 16:03:58 +09:00
Jeong, YunWon
19050afc3f Merge pull request #5520 from arihant2math/colorize
Add _colorize at 3.13.2
2025-02-14 15:34:26 +09:00
Ashwin Naren
e96557b3e1 add _colorize.py at 3.13.2
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 20:11:36 -08:00
Ashwin Naren
a5364973d9 implement nt._supports_virtual_terminal
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 20:11:05 -08:00
Ashwin Naren
a46ce8ec3a Mark version 3.13.0 (#5495)
* bump to 3.13.1
* fix some tests
* strip left whitespace from doc
* remove specific difflib test that was causing issues
* fix test_enum

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-13 14:11:01 +09:00
Ashwin Naren
6e35e20e49 dependency bump
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-02-12 18:16:45 +09:00
Noa
2d5e4d89b0 Update openssl to fix possible vulnerability 2025-02-12 15:57:41 +09:00
Hanif Ariffin
c9e62002ec Fixed the implementation of some math functions to match CPython closer. (#5510)
Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
2025-02-11 17:09:38 +09:00
Lee Dogeon
465627f104 Implement vm logics related with ParamSpec, TypeVarTuple 2025-02-10 21:21:38 +09:00
Ashwin Naren
3de1c2ab56 Update malachite-q and base to 0.4.22 (#5499)
* update malachite-q and base to 0.4.22

* Update malachite-bigint from parser

---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2025-02-10 15:55:08 +09:00
Jeong, YunWon
8f5cc6174c fix windows sleep 2025-02-07 07:53:28 +09:00
Jeong YunWon
29d014a0e1 Pin malachite versions to avoid API incompatibility 2025-02-03 11:57:30 +09:00
Ashwin Naren
396a0ca563 Basic Match statements (#5485)
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
2025-01-25 23:14:15 +09:00
Jeong YunWon
a500178b3c update parser to fix match crash 2025-01-22 13:41:01 +09:00
Jeong YunWon
7d770f55fb more assertions in switch_to_block 2025-01-21 23:53:23 +09:00
Jeong, YunWon
db283a66e8 Merge pull request #5477 from youknowone/better-downcast-error
Add better panic for abnormal downcast error
2025-01-21 13:54:45 +09:00
Jeong, YunWon
c642aef8ca Merge pull request #5481 from arihant2math/builtins-312
Update tests for builtin objects to python 3.12.8
2025-01-20 14:04:28 +09:00
Ashwin Naren
396df1a506 update test_listcomps.py to 3.12.8 2025-01-19 20:05:35 -08:00
Ashwin Naren
9c9fa7e537 update test_list to 3.12.8 2025-01-19 20:05:35 -08:00
Ashwin Naren
86e2eb0648 update test_float to 3.12.8 2025-01-19 20:05:34 -08:00
Ashwin Naren
491db2f0c6 update test_unary to 3.12.8 2025-01-17 18:04:25 -08:00
Ashwin Naren
f0fb375028 update numbers tests 2025-01-17 15:48:59 -08:00
Ashwin Naren
16d8bab61a Update Logging to 3.12.7 (#5478)
* updated logging to 3.12, added logging tests, and added smtplib and tests

* fix expected failures on test_smtplib.py

* mark all rustpython fails on test_logging.py
2025-01-17 19:44:06 +09:00
Ashwin Naren
2d83a67bd6 Update zlib from 3.12.6 and _ZlibDecompressor implementation (#5476)
* add is_s390x and skip_on_s390x to test support

* update zlib tests to 3.12

* _ZlibDecompressor implementation
2025-01-16 13:28:09 +09:00
Jeong YunWon
5ad7e97e05 Add better panic for abnormal downcast error 2025-01-16 00:57:09 +09:00
Jeong YunWon
b7a7b6b923 remove warnings from wasm build 2025-01-13 15:06:29 +09:00
Jeong YunWon
0e00d2328d wasm32-wasi -> wasm32-wasip1 2025-01-13 15:06:29 +09:00
Shubham Patil
53db70e784 Support recursion in JIT-ed functions (#5473) 2025-01-13 14:55:27 +09:00
Sacha Dupuydauby
76c699b4ba Update contextlib from CPython 3.12 2025-01-12 00:40:41 +09:00
Noa
c901bc07a4 Upgrade wasm deps + fix demo 2025-01-11 18:48:27 +09:00
Noa
b7db23bbae Fix warnings for Rust 1.84 2025-01-11 18:48:27 +09:00
Jeong, YunWon
389b20d977 Merge pull request #5444 from key262yek/update_fstring_from_v3.12.7
Update fstring from v3.12.7
2025-01-10 10:44:31 +09:00
Bob McWhirter
d06459fa49 guard signal-handling init more broadly
If `install_signal_handlers` is false (due to embedding),
the VM still inits the signal stdlib and installs a lot
of signal-handling, including touching *ever* signal,
including SIGINT.

When running multiple concurrent interpreters with
varying inits at varying times, this can break the
hosting application's signal-handling so lovingly
set up before starting anything with RustPython.
2025-01-09 16:21:25 -06:00
Shubham Patil
e2a55cbf34 Handle pre-release flag being empty for schedule triggers in release workflow
GitHub workflow_dispatch input variables are always empty for other triggers
2025-01-09 17:31:02 +09:00
Jeong, YunWon
a5e6ade9cb Merge pull request #5454 from coolreader18/rust-1.83
Bump MSRV to 1.83
2025-01-07 13:13:42 +09:00
Jeong, YunWon
a1e32566d3 Merge pull request #5469 from fu050409/patch-1 2025-01-07 12:38:08 +09:00
Noa
8c7bfb3e1a Fix redox 2025-01-06 13:09:49 -06:00
苏向夜
bb0480e978 docs(readme): fix installation command for cargo 2025-01-07 00:52:16 +08:00
Jeong, YunWon
2ccc745513 Merge pull request #5465 from crazymerlyn/caseless-bump 2025-01-04 11:43:30 +09:00
Jeong, YunWon
bea83fe94d Merge pull request #5466 from theshubhamp/gh-release 2025-01-04 11:43:10 +09:00
Shubham Patil
3feaf689d8 Re-enable Release Notes Generation 2025-01-03 21:49:17 +05:30
Shubham Patil
bd627b58af Schedule Pre-Release on Monday 9AM UTC 2025-01-03 21:49:17 +05:30
Shubham Patil
c561d33cb2 Support both Release & Pre-Release in Release Workflow
On Push "main" is removed
2025-01-03 21:49:17 +05:30
Ashwin Naren
c8fd3bd683 Build wasm on release 2025-01-03 14:56:58 +09:00
Ankit Goel
fef1e31634 Bump rust-caseless to 0.2.2 2024-12-31 12:26:29 +00:00
Shubham Patil
1abaf87abe Temporarily disable release notes generation
Release creation fails with this error:
```
HTTP 422: Validation Failed (https://api.github.com/repos/RustPython/RustPython/releases)
body is too long (maximum is 125000 characters)
Error: Process completed with exit code 1.
```

Most likely because there's no previous release to diff of from.

Disabling, getting a green release & enabling back might fix this.
2024-12-30 16:44:55 +09:00
Shubham Patil
38593fbd85 Check operand types in bool or, and, xor to be PyInt (#5461)
* Added Tests for Bitwise or, and, xor type error

* Sync binary operator order comment with actual implementation

* Check operand types in bool or, and, xor to be PyInt

PyNumber methods are expected to type check both arguments.

Dispatch is not done by inverting parameter order for __r<op>__ (example __ror__) when calls are handled via PyNumberMethods
2024-12-30 16:44:27 +09:00
Ankit Goel
8d187fd275 Bump result-like to 0.5.0 2024-12-28 11:38:00 +09:00
Shubham Patil
646cc81656 Add GitHub Binary Release Pipeline for RustPython (#5456) 2024-12-28 11:34:53 +09:00
carsonzhu
01f7536b36 expose run_shell 2024-12-11 17:33:36 +09:00
Jeong, YunWon
97e5ec02f8 Merge pull request #5449 from key262yek/update_test_float_from_CPython_v3.12.7
Update test float from c python v3.12.7
2024-12-06 12:50:55 +09:00
Oskar Skog
3dced01af0 Move os.system from posix.rs to os.rs
Fixes #5100
2024-12-06 12:19:34 +09:00
0cf4534c5c copy new file "Lib/test/support/testcase.py" for test_float.py
Some checks failed
CI / Run rust tests (macos-latest) (pull_request) Has been cancelled
CI / Run rust tests (windows-latest) (pull_request) Has been cancelled
CI / Ensure compilation on various targets (pull_request) Has been cancelled
CI / Run snippets and cpython tests (macos-latest) (pull_request) Has been cancelled
CI / Run snippets and cpython tests (ubuntu-latest) (pull_request) Has been cancelled
CI / Run snippets and cpython tests (windows-latest) (pull_request) Has been cancelled
CI / Check Rust code with rustfmt and clippy (pull_request) Has been cancelled
CI / Run tests under miri (pull_request) Has been cancelled
CI / Check the WASM package and demo (pull_request) Has been cancelled
CI / Run snippets and cpython tests on wasm-wasi (pull_request) Has been cancelled
CI / Run rust tests (ubuntu-latest) (pull_request) Has been cancelled
2024-12-05 15:29:35 +09:00
044f66fba3 copy from cpython v3.12.7 2024-12-05 15:29:35 +09:00
40a9ddad4e update test_fstring.py from cpython 3.12.7
add expectedFailure to tag what should rustpython do
add comment for some syntaxerror which make test run broken
2024-12-05 15:04:56 +09:00
Noa
848db340da Merge pull request #5442 from coolreader18/cli-fixes
Miscellaneous cli-related parity fixes
2024-12-03 23:57:57 -06:00
Noa
c6da4ffcdd Try to fix universal write on windows 2024-12-03 18:01:44 -06:00
Noa
8ac7e34be2 Updates for Rust 1.83 2024-12-03 17:05:24 -06:00
Noa
e4be882994 Miscellaneous cli-related parity fixes 2024-12-03 16:28:32 -06:00
Noa
adc05e663f Merge pull request #5443 from coolreader18/incremental-newline-decoder
Implement IncrementalNewlineDecoder in rust
2024-12-03 16:28:00 -06:00
Jeong, YunWon
0bc236ac98 Merge pull request #5450 from key262yek/resolve_lint_fails_w_rust_1.83_clippy
Resolve lint fails w rust 1.83 clippy
2024-12-02 12:42:37 +09:00
da3d3d9296 allow manual c string literal 2024-11-29 11:57:42 +09:00
6a6af6a952 change to use c str literal 2024-11-29 11:19:10 +09:00
582a4ab267 delete elided lifetime 2024-11-29 11:18:53 +09:00
ebde8546c9 remove elided lifetime 2024-11-29 11:05:44 +09:00
fffd0f5465 new clippy lint update Rust v1.83.0 2024-11-29 09:53:54 +09:00
dependabot[bot]
3b6db8e21a Bump the github-actions group with 7 updates
Bumps the github-actions group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `3` | `4` |
| [actions/setup-python](https://github.com/actions/setup-python) | `4` | `5` |
| [actions/setup-node](https://github.com/actions/setup-node) | `3` | `4` |
| [mwilliamson/setup-wabt-action](https://github.com/mwilliamson/setup-wabt-action) | `1` | `3` |
| [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) | `2` | `4` |
| [wasmerio/setup-wasmer](https://github.com/wasmerio/setup-wasmer) | `2` | `3` |
| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `3` | `5` |


Updates `actions/checkout` from 3 to 4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

Updates `actions/setup-python` from 4 to 5
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

Updates `actions/setup-node` from 3 to 4
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

Updates `mwilliamson/setup-wabt-action` from 1 to 3
- [Release notes](https://github.com/mwilliamson/setup-wabt-action/releases)
- [Commits](https://github.com/mwilliamson/setup-wabt-action/compare/v1...v3)

Updates `peaceiris/actions-gh-pages` from 2 to 4
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v2...v4)

Updates `wasmerio/setup-wasmer` from 2 to 3
- [Release notes](https://github.com/wasmerio/setup-wasmer/releases)
- [Commits](https://github.com/wasmerio/setup-wasmer/compare/v2...v3)

Updates `codecov/codecov-action` from 3 to 5
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: mwilliamson/setup-wabt-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: peaceiris/actions-gh-pages
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: wasmerio/setup-wasmer
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-27 17:44:35 -07:00
Noa
19a0b7dd76 Merge pull request #5445 from cclauss/patch-2
Keep GitHub Actions up to date with GitHub's Dependabot
2024-11-27 13:21:11 -06:00
Noa
9647ebe206 Fix prettier formatting 2024-11-27 13:20:19 -06:00
Noa
0c726a1275 Merge pull request #5447 from isbm/isbm-move-exception-loglevel
Move log message to the debug level
2024-11-27 13:04:03 -06:00
Jeong, YunWon
f71eb55f1f Merge pull request #5438 from key262yek/update_random_from_v3.12.3
Update random from v3.12.7
2024-11-26 16:33:36 -07:00
Jeong, YunWon
edcc0b7dbc Merge pull request #5437 from key262yek/update_numbers_from_v3.12.3
Update numbers.py from v3.12.7
2024-11-26 16:30:37 -07:00
Buciu Theodor Marian
4910b308ee Fix compile error when using ``vm-tracing-logging`` 2024-11-26 16:26:00 -07:00
Bo Maryniuk
0cc1c323ed Move log message to the debug level 2024-11-26 21:29:08 +01:00
Christian Clauss
24fd19b35d Keep GitHub Actions up to date with GitHub's Dependabot
Fixes software supply chain safety warnings like at the bottom right of

https://github.com/RustPython/RustPython/actions/runs/11870777239
* [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)
* [Configuration options for the dependabot.yml file - package-ecosystem](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem)
2024-11-26 16:38:54 +01:00
Noa
fbd0c7a99e Implement IncrementalNewlineDecoder in rust 2024-11-14 23:06:53 -06:00
4f5b26698c skip test 'super' object has no attribute 'getstate' 2024-11-11 00:17:57 +09:00
d887e5c24c first copy from cpython v3.12.3 2024-11-11 00:17:30 +09:00
0c69391bbd Copy test for numbers.py from v3.12.3 2024-11-10 00:15:21 +09:00
91598f9121 change numbers.py from [v3.12.3](6293d00e72/Lib/numbers.py (L8)) 2024-11-10 00:13:13 +09:00
Jeong YunWon
98d09e7816 Remove time.daylight from freebsd 2024-10-31 16:13:52 +09:00
7ae9432524 Change deprecated type PanicInfo to PanicHookInfo 2024-10-31 01:45:59 +09:00
Noa
c883f0ad8a Updates for Rust 1.82 2024-10-17 16:32:47 -05:00
Noa
eae60113af Update some stuff for inline const & associated type bounds 2024-10-17 16:32:17 -05:00
Noa
1aab5240cf Update for rust 1.77 2024-10-17 16:32:17 -05:00
dependabot[bot]
edb7abde5a Bump pyo3 from 0.22.3 to 0.22.4
Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.3 to 0.22.4.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.22.3...v0.22.4)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-16 05:37:46 +09:00
Noa
29d95340b0 Merge pull request #5418 from crazymerlyn/memoryerror
Handle MemoryError for sequences
2024-09-29 10:50:01 -05:00
Ankit Goel
6fb19ac74f Handle MemoryError for sequences 2024-09-29 16:24:30 +01:00
Noa
37dc28a69d Update deps (#5417)
* Upgrade nix

* Update deps

* Upgrade pyo3, winreg

* Fix errors from upgrading
2024-09-27 13:11:00 +09:00
Ankit Goel
7623668256 Raise TypeError if BaseException receives Keyword arguments 2024-09-24 11:57:37 +09:00
Noa
bbf7aacd4d Merge pull request #5409 from crazymerlyn/cache-frozenset-hash
Cache hash value for FrozenSets
2024-09-23 17:43:55 -05:00
Noa
d6c1e08ef4 Merge pull request #5413 from crazymerlyn/remove-proc-macro-crate
Remove dependency on proc-macro-crate
2024-09-23 17:42:40 -05:00
Ankit Goel
d1f95f04a7 Remove dependency on proc-macro-crate
This dependency is only useful when modifying the crate name of
`num_enum` crate. RustPython doesn't do that so it's unnecessary.
2024-09-23 16:54:21 +01:00
Ankit Goel
0dacf8a326 Remove dependency on uuid-macro-internal
`uuid-macro-internal` is only relevant when using `uuid!` macro to parse
uuids at compile time. RustPython doesn't do that so it's unnecessary.
2024-09-23 15:07:57 +09:00
Noa
e534b10722 Fix build with ossl1 2024-09-22 17:38:26 +09:00
Jeong, YunWon
0785cc5aa9 Merge pull request #5406 from coolreader18/improve-posixsubprocess
Improve posixsubprocess
2024-09-22 17:37:34 +09:00
Ankit Goel
48025e0102 Cache hash value for FrozenSets
Adds a hash field to the `PyFrozenSet` data type in order to avoid
recomputing the hash of an immutable object.
2024-09-22 06:15:54 +01:00
Noa
eeb719e8f7 Merge pull request #5411 from crazymerlyn/decimal-from-float
Fix inconsistent behavior of Decimal.from_float
2024-09-21 22:58:23 -05:00
Noa
7933edad43 Add missing functionality to posixsubprocess 2024-09-21 22:40:15 -05:00
Ankit Goel
5f75728ede Fix inconsistent behavior of Decimal.from_float 2024-09-22 01:42:28 +01:00
Ankit Goel
8cff0ed6c2 Avoid allocating a vector of elements when hashing frozenset (#5408)
Adds a `try_fold_keys` method to Dict which allows performing common
operations on all elements without needing to create a Vec first.
2024-09-21 23:18:26 +09:00
Noa
a8964f4108 Add select.epoll 2024-09-20 11:46:01 +09:00
Noa
740aeedca3 Merge pull request #5405 from crazymerlyn/fix-set-intersection-update
Fix set intersection_update implementation
2024-09-19 21:21:16 -05:00
Noa
8152e7e62c Make Gid/Uid less janky 2024-09-19 17:50:52 -05:00
Ankit Goel
b36c95b91e Fix set intersection_update implementation 2024-09-19 20:59:12 +01:00
Ricardo Robles
b5c1fd95dc Add zoneinfo from Python 3.12.6 (#5400) 2024-09-19 11:18:48 +09:00
Noa
23f7cbf1c3 Make sqlite optional 2024-09-19 11:13:51 +09:00
hongmengning
ae78ecc2c5 Add missing symbols in exceptions.rs 2024-09-17 16:07:30 +09:00
Ankit Goel
dd06516d1c Deprecate delegating int() to __trunc__ 2024-09-17 16:06:09 +09:00
Ankit Goel
8066f36a4e Fix copysign behavior for NaNs (#5396) 2024-09-11 15:19:21 +09:00
Jeong YunWon
3bbf6b9d53 less strict time bound for test_re.test_search_anchor_at_beginning 2024-09-10 16:31:43 +09:00
dependabot[bot]
8bc5944178 Bump webpack from 4.28.2 to 5.94.0 in /wasm/example
Bumps [webpack](https://github.com/webpack/webpack) from 4.28.2 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.28.2...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 14:14:51 +09:00
Niels Buwen
a13b99642b Add get/set methods for function module/annotations (#5392)
---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2024-08-19 00:59:51 +09:00
Jeong, YunWon
060db5983c Merge pull request #5390 from youknowone/wasmbind
recreating wasmbind patch
2024-08-09 09:37:49 +09:00
Jeong YunWon
42bba6920e apply suggestion 2024-08-09 02:23:49 +09:00
Noa
ea11d78995 Fix compilation without compiler feature 2024-08-09 02:23:49 +09:00
Jeong YunWon
fe63ca762f separate wasm-unknown-test 2024-08-09 02:23:49 +09:00
Jeong YunWon
a82982725e fix getrandom 2024-08-09 02:23:49 +09:00
Noa
7dfb760421 Make rustpython-vm compatible with non-js wasm32-unknown & add tests 2024-08-09 02:04:25 +09:00
Jeong YunWon
b6e9a3f37e rustpython_wasm uses the new feature 2024-08-09 00:59:29 +09:00
Jeong YunWon
3f28309b7b revert unnecessary change 2024-08-09 00:58:53 +09:00
Jeong YunWon
d2a4a330f9 following chrono/wasmbind convention 2024-08-09 00:58:11 +09:00
Benjamin DeMann
d8c35770ab enable js feature of getrandom only for wasmbind 2024-08-09 00:29:47 +09:00
Benjamin DeMann
dbb6794a41 add cfg for not wasmbind for time 2024-08-09 00:29:25 +09:00
Benjamin DeMann
63c9909aa0 run the first block if wasmbind is not present 2024-08-09 00:29:25 +09:00
Benjamin DeMann
f1dac5087e address pr notes 2024-08-09 00:29:25 +09:00
Benjamin DeMann
4f80d7013e add wasmbind feature 2024-08-09 00:29:25 +09:00
Jeong YunWon
2919df1df5 Mark rust-version 1.78 2024-08-08 23:20:17 +09:00
Jeong YunWon
a2df2f014b Fix dis.dis without compiler feature 2024-08-07 09:48:09 +09:00
Jeong YunWon
8673169ee7 0.4.0 2024-08-07 08:38:21 +09:00
Jeong YunWon
2c2e0fb172 pyperf bench script 2024-08-07 08:38:21 +09:00
Jeong YunWon
d45c5de906 ignore local config files 2024-08-07 08:38:21 +09:00
Jeong YunWon
eb985beed6 freeze-stdlib imply stdlib 2024-08-07 07:48:42 +09:00
Jeong YunWon
ff9aa0fc54 Add example projects 2024-08-07 07:48:42 +09:00
Jeong YunWon
0bd1a3efb2 vm.print 2024-08-07 07:06:27 +09:00
Jeong YunWon
08e7ec948b Don't let Interpreter::run silently ignore result value 2024-08-07 07:06:15 +09:00
Jeong YunWon
6092c07eb5 better error message for freeze-stdlib 2024-08-07 07:05:28 +09:00
CPython Developers
09d74336fa Update fraction,test_numeric_tower from CPython 3.12.4 2024-07-29 19:53:44 +09:00
Jeong YunWon
694354e5e6 temp fix slot member_count 2024-07-29 16:45:35 +09:00
Andrew Bowen
55f04db6b8 Update codecs and test_codecs from cpython v3.12.4 (#5372)
* updated test_codecs.py from cpython v3.12.4

* updated codecs.py from cpython v3.12.4
2024-07-29 14:55:32 +09:00
Jeong YunWon
6f8360a878 sys.exception 2024-07-29 14:30:59 +09:00
Jeong YunWon
6ca4685fee __objclass__ 2024-07-29 14:30:46 +09:00
Jeong, YunWon
d86b592add Merge pull request #5370 from youknowone/update-io
Update io, _pyio, test_io from CPython 3.12
2024-07-28 21:12:13 +09:00
Jeong YunWon
9944b3dac1 Mark failing tests 2024-07-28 20:24:15 +09:00
Jeong YunWon
8ed79bd1af add a dummy parameter for support.skip_if_sanitizer 2024-07-28 20:24:15 +09:00
CPython Developers
0600ae6213 Update io, pyio, test_io from CPython 2024-07-28 20:24:15 +09:00
Jeong YunWon
623415d843 IncrementalNewlineDecoder from pyio 2024-07-28 20:23:53 +09:00
Jeong YunWon
87b84a83cc Fix TextIOWrapper.reconfigure coder 2024-07-28 20:23:53 +09:00
Jeong YunWon
aa5eba9723 fix BufferedMixin::tell 2024-07-28 20:23:53 +09:00
Jeong YunWon
c2cd883f93 TextIOWrapper.reconfigure 2024-07-28 16:24:32 +09:00
Jeong YunWon
43e20a8cd9 Fix os.putenv & test_os.test_envronb 2024-07-28 16:24:17 +09:00
Jeong YunWon
48299cdd7f Add faulthandler to wasm32 2024-07-28 12:11:56 +09:00
Dan Näsman
2335ca0f72 Add proper underscore handling to float and complex types. (#5356) 2024-07-28 12:11:34 +09:00
Jeong YunWon
e42c1a33b5 upgrade unicode_names2 2024-07-28 11:43:00 +09:00
Jeong, YunWon
b1a38c2d50 Fix rust 1.80 warnings (#5363)
* Fix rust 1.80 warnings

* Fix nightly clippy warnings
2024-07-28 10:43:40 +09:00
dependabot[bot]
f1f05303db Bump openssl from 0.10.62 to 0.10.66
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.62 to 0.10.66.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.62...openssl-v0.10.66)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-23 11:14:45 +09:00
Jeong YunWon
8424a733a2 type constructor 2024-07-14 22:08:09 +09:00
Jeong YunWon
2bf7a4a08c FuncArgs::is_empty() 2024-07-14 22:08:09 +09:00
Jeong, YunWon
b4bae8173b Merge pull request #5348 from JamesClarke7283/developer-experiance-tweaks
DX improvements: Updated MSRV, added cargo deny toml file, and added lints to workspace
2024-07-01 21:09:49 +09:00
James Clarke
3c10888e3a Disabled some uneeded lits 2024-06-30 13:41:40 +01:00
James Clarke
1bd143027a Switched to system implementation of libffi and fixed minor lint errors 2024-06-30 13:29:04 +01:00
James Clarke
18d31f2d5b Merge branch 'RustPython:main' into developer-experiance-tweaks 2024-06-30 12:34:12 +01:00
James Clarke
e142d655b9 changed int_max_str_digits from -1 to 4300 to be more cpython complient (#5343)
* changed int_max_str_digits from -1 to 4300 to be more cpython complient, fixes #5139
2024-06-27 15:27:54 +09:00
James Clarke
e1ce1f66cd Updated MSRV 2024-06-25 04:56:22 +01:00
James Clarke
08c9a4d86b Added lints and deny.toml 2024-06-25 03:59:40 +01:00
Kirill Podoprigora
a620b38ba0 Add test_copyreg.py from 3.12 2024-06-24 19:00:54 +09:00
Jeong, YunWon
8e3c0cd658 Merge pull request #5314 from youknowone/datetime
Update datetime from CPython v3.12.3
2024-06-22 17:38:59 +09:00
Jeong YunWon
f709a2805d mark failing tests 2024-06-22 16:18:34 +09:00
Jeong, YunWon
adbadfc4f5 Merge pull request #5342 from youknowone/getstate
object.__getstate__
2024-06-22 15:49:23 +09:00
CPython Developers
9c7a9cbace Update datetime from CPython v3.12.3 2024-06-22 15:27:19 +09:00
CPython Developers
1333688a4e update copyreg from CPython 3.12.3 2024-06-22 15:24:42 +09:00
Jeong YunWon
17852b78d5 mark hanging test 2024-06-22 15:24:42 +09:00
Jeong YunWon
dd15ae5560 Unmark successful tests 2024-06-22 15:24:42 +09:00
Jeong YunWon
9d33990199 object.__getstate__ 2024-06-22 15:15:49 +09:00
Cherry
866e7cf371 Make a shared run function in ThreadedVirtualMachine (#5337) 2024-06-22 14:43:07 +09:00
Jeong YunWon
f5c1596ddf PyCFunction_GET_SELF 2024-06-22 00:40:30 +09:00
Jeong YunWon
64cff172a1 pymethod(raw) 2024-06-22 00:40:13 +09:00
kingler
c10b99b29a Update keyword.py and test_keyword.py from CPython v3.12.0 2024-06-21 22:37:15 +09:00
Snowapril
bca90f191c Fix missing_transmute_annotations, legacy_numeric_constants added in Rust 1.79.0 clippy (#5339) 2024-06-16 20:26:22 +09:00
Jonathan Rotter
7996a10116 await in list comprehension (#5334)
* check if comprehension element contains await

* force execution to pause in async gen
2024-05-27 16:54:56 +09:00
Dmitry Mottl
db4562f67d Fixes a typo 2024-05-22 10:07:44 +09:00
Noa
3fd0382a51 Use RPITIT where applicable 2024-05-21 14:47:10 +09:00
Noa
3e98e5e86c Bump libsqlite3-sys 2024-05-21 09:05:49 +09:00
Jeong YunWon
7b17965b26 Enable macOS SSL 2024-05-21 09:05:26 +09:00
Jeong YunWon
5c050d5779 feature ssl-vendor implies ssl 2024-05-20 19:41:51 +09:00
Jeong, YunWon
5d6d1a6da7 OpenSSL3 in README 2024-05-20 13:07:36 +09:00
Daniel Chiquito
b59d876e76 Make __bases__ mutable (#5325)
* Make PyType.bases and PyType.mro mutable

* Add a set function for __bases__

* Re-enable fixed test_descr tests
2024-05-20 12:28:07 +09:00
Jeong YunWon
9028aede6e Reduce rc ops from best_base 2024-05-19 19:24:52 +09:00
Snowapril
aa0353a501 Fix best_base to select proper base class (#5324)
* add __basicsize__ getter

* modify best_base function to get proper base

* inherit basicsize from the base type

---------

Signed-off-by: snowapril <sinjihng@gmail.com>
2024-05-19 15:48:56 +09:00
Jeong YunWon
63c3f31c99 skip flaky test in test_context 2024-05-17 14:02:40 +09:00
Jeong YunWon
2fe44af8ba time.{tzname,timezone,daylight} 2024-05-14 11:17:58 +09:00
matheusfgoncalves95
515f0bf9c2 Adding generator_stop to compile_future_features (#5320)
* Adding generator_stop to compile_future_features.

* Removing expected failure from PEP 479 unit test.
2024-05-13 23:18:55 +09:00
hydrogen602
27a52a7962 test_class.py: testTypeAttributeAccessErrorMessages fix 2024-05-13 16:25:37 +09:00
hydrogen602
61feb43aba test_builtin.py: test_type_qualname fix 2024-05-13 14:18:18 +09:00
Jonathan Rotter
07fdcb6160 Fix for test future.py test annotations (#5319)
* enable test fixed in parser

* update parser dependency
2024-05-13 08:40:14 +09:00
Daniel Chiquito
ac08f4447f Add 3.12 typing features to the compiler (#5302) 2024-05-10 20:09:04 +09:00
Jeong, YunWon
d243c90d0a Update site from CPython v3.12.3 (#5313)
* Update site from CPython v3.12.3

* mark failing test

---------

Co-authored-by: CPython Developers <>
2024-05-09 23:04:31 +09:00
Jeong, YunWon
72033ab689 Update ipaddress from CPython v3.12.3 (#5312)
Co-authored-by: CPython Developers <>
2024-05-09 15:56:11 +09:00
Jeong, YunWon
3f63501fa8 Merge pull request #5311 from youknowone/pathlib
Update pathlib from CPython v3.12.3
2024-05-09 15:46:25 +09:00
Jeong YunWon
019496e3a8 Instruction::ReturnConst 2024-05-09 15:26:16 +09:00
Jeong YunWon
52695a9ece Skip tests to avoid slot bug 2024-05-09 15:20:38 +09:00
CPython Developers
a20ce951e7 Update pathlib from CPython v3.12.3 2024-05-09 15:20:25 +09:00
Jeong YunWon
85fa157d5a Update test_os from CPython v3.12.3 2024-05-09 11:51:05 +09:00
Jeong, YunWon
1068219c56 Update Lib/asyncio from CPython v3.12.3 (#3858)
Co-authored-by: CPython Developers <>
2024-05-09 05:02:46 +09:00
Jeong, YunWon
427ec50624 _overlapped 2024-05-09 04:41:39 +09:00
Jeong, YunWon
52ce1509a5 optimize range.__contains__ and iter_search 2024-05-09 01:26:09 +09:00
Jeong YunWon
fe06583643 posix.sysconf 2024-05-08 18:46:17 +09:00
Jeong YunWon
3e3c69b5bc Fix non-msvc build 2024-05-08 16:30:41 +09:00
Jeong YunWon
3b0802535d _winapi.NULL 2024-05-08 16:30:41 +09:00
Jeong YunWon
75415090bd ConfName -> PathConfName 2024-05-08 14:50:51 +09:00
Jeong YunWon
6f664cb05a Fix initgroups nul error 2024-05-08 14:50:51 +09:00
Jonathan Rotter
a8ea67178d adjusted SyntaxError args and __str__() to match CPython (#5294) 2024-05-08 12:57:03 +09:00
Jeong, YunWon
64a464aefb Update audiotests from CPython 3.12.3 (#5304)
Co-authored-by: moonlightaria <edch66@gmail.com>
2024-05-08 12:52:33 +09:00
jparag
67885ad9fa Update gettext.py and related test to 3.12 version (#5287)
Co-authored-by: Parag Jain <parag.jain2@capitalone.com>
2024-05-08 00:02:59 +09:00
Jeong YunWon
e5bf72e897 contextvars 2024-05-07 22:43:21 +09:00
Jeong YunWon
1f62190eef Use Sequence protocol for in operation 2024-05-07 21:33:14 +09:00
Jeong YunWon
f839e6cc79 more #[pyclass(Py, PyRef)] 2024-05-06 01:32:46 +09:00
Jeong YunWon
52aad1ec08 DefaultConstructor impls Constructor; Unconstructible is not anymore 2024-05-06 00:54:39 +09:00
Jeong YunWon
5f0d1252b6 suppress empty-doc warning 2024-05-04 02:00:18 +09:00
Jeong YunWon
3370f0f23d Update wasm-bindgen 2024-05-04 02:00:18 +09:00
Jeong YunWon
e1574b1485 sys.{get,set}_asyncgen_hooks 2024-05-02 13:45:03 +09:00
Jeong YunWon
78fae736f9 sys.get_coroutine_origin_tracking_depth 2024-05-01 21:32:14 +09:00
Jeong YunWon
ed3811a65c wasi select module 2024-05-01 17:54:24 +09:00
Jeong, YunWon
4e915de35d Merge pull request #5290 from youknowone/subprocess
Update subprocess from CPython 3.12.3
2024-05-01 16:24:35 +09:00
CPython Developers
94e6648ee5 Update selectors from CPython v3.12.3 2024-05-01 15:59:23 +09:00
Jeong YunWon
75a985e63e mark failng tests 2024-05-01 15:59:23 +09:00
CPython Developers
5714558785 Update subprocess from CPython 3.12.3 2024-05-01 04:23:14 +09:00
Jeong, YunWon
c3ccb5b7bf Merge pull request #5289 from youknowone/fixes
Align Settings to CPython PyConfig
2024-04-30 23:18:43 +09:00
Jeong YunWon
87849cda4f fix grammar 2024-04-30 23:18:14 +09:00
Jeong YunWon
f62114c7eb zlib-ng as default 2024-04-30 23:18:14 +09:00
Jeong YunWon
24f57dde2f Align Settings to PyConfig 2024-04-30 23:18:14 +09:00
Jeong, YunWon
cdfcd8a4c9 Merge pull request #5288 from youknowone/enable-wasi-unittest
Enable wasi unittest
2024-04-30 23:13:07 +09:00
Jeong YunWon
1bc1370701 Add wasi unittest CI 2024-04-30 22:21:56 +09:00
Jeong YunWon
6b7b080827 Add sys._stdlib_dir when freeze-stdlib 2024-04-30 22:21:56 +09:00
Jeong YunWon
3556e1320d wasm os.getpid 2024-04-29 14:42:02 +09:00
Jeong YunWon
621640ca71 Add const-only signals for wasm32 2024-04-28 10:31:38 +09:00
Jeong YunWon
2c0e439d0d Disable jit for macos from CI to avoid arm64 CI failure 2024-04-28 00:07:55 +09:00
Jeong YunWon
a392d8425e Fix wasmer example 2024-04-26 20:57:32 +09:00
Jeong YunWon
0273d78de9 Update signal from CPython 3.12.3 2024-04-26 01:39:02 +09:00
Jeong YunWon
ed51d8dcf6 upgrade geckodriver 2024-04-25 23:11:56 +09:00
Jeong, YunWon
3d78ca8b09 Merge pull request #5263 from youknowone/multiprocessing
Update multiprocessing from CPython 3.12.3
2024-04-25 22:05:11 +09:00
Jeong YunWon
1cec856c63 skip flaky test_socket sendmsg tests in macos 2024-04-25 22:04:40 +09:00
Jeong, YunWon
6212c81ec6 Merge pull request #5276 from youknowone/async-for-comprehension
Async for comprehension
2024-04-25 21:00:22 +09:00
Jeong YunWon
408459b883 vm runtime debug prints 2024-04-25 20:34:53 +09:00
Jeong YunWon
f6d88042b5 Uncomment test_grammar tests 2024-04-25 20:23:34 +09:00
Jeong YunWon
b58bdc9e32 Uncomment async for tests 2024-04-25 20:23:34 +09:00
Jeong YunWon
f3501f44cb Basic async for comprehension support 2024-04-25 20:23:25 +09:00
HyeockJinKim
61f37b10e2 implement async for function in compiler 2024-04-25 20:20:58 +09:00
Aaron Long
2856aff757 Begin async list comprehension implementation 2024-04-25 20:20:22 +09:00
Jeong YunWon
3949ecc054 implement more GetANext 2024-04-25 19:16:48 +09:00
Jeong YunWon
acd9ea57d7 Fix async for block 2024-04-25 17:40:15 +09:00
Jeong YunWon
794a2a1892 remove pop-push 2024-04-25 17:40:15 +09:00
hydrogen602
af8bed6d3f filter out doc strings with opt level 2 2024-04-25 16:11:04 +09:00
Jeong YunWon
462f54f906 Fix with __exit__ error handling 2024-04-25 15:17:50 +09:00
Jeong YunWon
1c4e99cf2c popblock debug 2024-04-25 15:17:50 +09:00
Jeong YunWon
a349b9bdfe frame debug codes 2024-04-24 21:46:14 +09:00
Jeong YunWon
75e790836a debuggable 2024-04-24 20:44:17 +09:00
hardlydearly
5ba677cd36 chore: fix some typos in comments
Signed-off-by: hardlydearly <799511800@qq.com>
2024-04-24 17:52:58 +09:00
Jeong, YunWon
a7b761a89c windows symlink flag 2024-04-24 02:43:01 +09:00
Jeong, YunWon
6e0b00d60c Merge pull request #5262 from youknowone/ntpath
Update ntpath from CPython 3.12.3
2024-04-24 01:46:49 +09:00
Jeong YunWon
c107a938be remove -u all from mac/windows 2024-04-24 01:46:27 +09:00
Jeong YunWon
c6fc9cee8e Update filecmp from CPython 3.12.2 2024-04-24 01:46:27 +09:00
Jeong YunWon
99a4bf5eb5 remove import dummp_os 2024-04-24 01:46:27 +09:00
CPython Developers
46410ff933 Add test_multiprocessing from CPython 3.12.3 2024-04-24 01:15:34 +09:00
CPython Developers
75e868e71e Update multiprocessing from CPython 3.12.3 2024-04-24 01:15:08 +09:00
Jeong, YunWon
53b1b4d682 Mark failing tests in test_ntpath 2024-04-24 00:54:12 +09:00
CPython Developers
566d9bee5c Update ntpath from CPython 3.12.3 2024-04-24 00:54:12 +09:00
Jeong, YunWon
b8bf65d68e os.listdrives 2024-04-24 00:54:09 +09:00
Jeong YunWon
075c69a3cd Skip setattr(__doc__) if not given 2024-04-23 22:44:22 +09:00
Jeong, YunWon
ada10067dd Merge pull request #5260 from youknowone/update-pickle
Update pickle from CPython 3.12.3
2024-04-23 21:37:32 +09:00
CPython Developers
d0f680b379 Update pickletools from CPython 3.12.3 2024-04-23 18:05:50 +09:00
Jeong YunWon
ca2c1d0b48 Update pickle from CPython 3.12.2 2024-04-23 16:46:16 +09:00
Jeong YunWon
5fd5939395 skip flaky socket test in macOS 2024-04-23 16:24:15 +09:00
Jeong, YunWon
e5ca631b52 Add bare ExceptionGroup (#5259) 2024-04-23 16:23:31 +09:00
Jonathan Rotter
3313fdeb32 test_builtin.py test_compile unit test fix (#5251)
* compile accepts memoryview now

* Update test_compile to what it is in CPython3.12

* compile optimize flag

* added undocumented flags to flag validator
2024-04-23 14:07:02 +09:00
Jeong, YunWon
2bd8ff0b6c Merge pull request #5257 from youknowone/os-posix
Update os/posix/posixpath from CPython 3.12.2
2024-04-23 13:33:40 +09:00
CPython Developers
5c9d6d455d Update genericpath/posixpath from CPython 3.12.2 2024-04-23 12:56:43 +09:00
CPython Developers
8f6cf6fef7 Update test_posix from CPython 3.12.2 2024-04-23 12:56:43 +09:00
CPython Developers
a5f8d42d34 Update os from CPython 3.12.2 2024-04-23 12:56:32 +09:00
CPython Developers
153ec2823d Update __future__ from CPython 3.12.2 2024-04-23 11:57:46 +09:00
CPython Developers
9f65a30504 Update encodings from CPyhton 3.12.2 2024-04-22 12:46:32 +09:00
CPython Developers
b018123f19 Update getopt from CPython 3.12.2 2024-04-22 12:46:32 +09:00
CPython Developers
97e3e969e4 Update glob from CPython 3.12.2 2024-04-22 12:46:32 +09:00
Noa
84099514e6 Implement socket.socket.sendmsg (#5205)
* Implement socket.socket.sendmsg

* debugger-friendly newlines

* Fix control_buf error on macOS

---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2024-04-22 11:21:10 +09:00
Jeong, YunWon
3286e683e6 win32_xstat (#5247)
* win32_xstat

* vm::windows

* Add file_id
2024-04-21 22:02:09 +09:00
hydrogen602
be6ea2a7b8 fix for test test_bool.BoolTest.test_subclass 2024-04-21 07:09:08 +09:00
Jeong, YunWon
4a939141f6 Fix macos shutil (#5248)
* Fix macos shutil

* chmod+follow_symlinks=False calls lchmod
2024-04-21 07:07:58 +09:00
Jonathan Rotter
1034477f1e unit test fix - only allow catching exceptions - test_catch_non_BaseException (#5249)
* test_baseexception.test_catch_non_BaseException

* enabling test_except_star_invalid_exception_type
2024-04-21 01:30:20 +09:00
Jeong, YunWon
49cfcd817d remove some skipIf and enable shutil CI for windows (#5235)
* mark failing shtuil tests in windows

* unsetenv raise more informed error

* Remove a few skipIf for windows
2024-04-20 03:00:27 +09:00
Jeong, YunWon
39822d7e57 use less vm.new_os_error (#5245)
* vm.new_errno_error

* replace new_os_error in mmap

* replace posix new_os_error to new_errno_error
2024-04-20 01:32:33 +09:00
Jeong YunWon
142d333c5c Fix os.unsetenv 2024-04-20 00:36:00 +09:00
Jeong YunWon
13c491712b add six install to CI 2024-04-19 22:45:25 +09:00
Jeong, YunWon
5c527c2a28 Fix shutil test_copy_dir 2024-04-19 22:04:22 +09:00
toofooboo
41979f0823 chore: fix some typos in comments (#5243)
Signed-off-by: toofooboo <cmaker@foxmail.com>
2024-04-19 21:10:22 +09:00
Jeong, YunWon
192b0a8fb5 fileutils and fstat (#5242) 2024-04-19 19:21:35 +09:00
Jonathan Rotter
6a7be1e142 compat fix for testHashStuff in test_class.py (#5240)
* compat fix for testHashStuff in test_class.py

* accidently fixed 3 more tests
2024-04-19 16:44:23 +09:00
Jeong, YunWon
b3666060d4 better OSError.filename (#5239)
* Fix OSError message

* IOError with filename

* more filenames
2024-04-19 15:53:11 +09:00
Jeong, YunWon
c9c07a6bbc vm/ospath module 2024-04-19 13:25:19 +09:00
Jeong, YunWon
d57287b89b Fix OSError.winerror and errno/winerror converions 2024-04-19 04:56:25 +09:00
Jeong, YunWon
79e7015a32 CreateJunction 2024-04-19 03:00:33 +09:00
Jeong, YunWon
6c4ee951e0 fix windows OSError 2024-04-18 10:23:12 +09:00
Jeong YunWon
f0f4633346 Fix nightly clippy 2024-04-18 03:37:29 +09:00
hydrogen602
6c37e8f4e7 compat fix for test_field_metadata_custom_mapping in test_dataclasses.py 2024-04-17 21:52:10 +09:00
CPython Developers
4d05416ce3 Update ensurepip from CPython 3.12 2024-04-17 12:03:31 +09:00
Lee Dogeon
60993b9d23 Bump shutil to 3.12.3 (#5228)
* Bump shutil to 3.12.3

* Implement windows platform target `is_junction` with junction library

* Add _winapi.NeedCurrentDirectoryForExePath

---------

Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2024-04-17 01:33:14 +09:00
Moreal
959e7c11ce Unmark fixed tests 2024-04-15 16:22:53 +09:00
Moreal
21ae739eec Check downcast to type first 2024-04-15 16:22:53 +09:00
Moreal
b1c3c9a9d6 Bump CPython version to 3.12.3 in CI 2024-04-14 13:07:13 +09:00
Moreal
bf985c8ac6 Fix None.__ne__ bug 2024-04-14 13:07:13 +09:00
Moreal
21c5eae717 Add testcase for __ne__ method 2024-04-14 13:07:13 +09:00
Lee Dogeon
4d5cf249a0 Allow update __class__ for mutable types or module subclasses (#5225)
* Allow update `__class__` for module subclasses

* Set `IMMUTABLETYPE` flag for static classes

* Unmark fixed tests
2024-04-14 13:03:48 +09:00
Jeong YunWon
8c7b811135 Fix _sha512 support 2024-04-11 21:17:53 +09:00
Jeong, YunWon
940b879950 Merge pull request #5222 from youknowone/vm-build
Fix rustpython-vm --no-default-features build
2024-04-11 15:29:43 +09:00
Jeong YunWon
ccf650d4ca Fix rustpython-vm --no-default-features build 2024-04-10 19:15:22 +09:00
Jeong, YunWon
6342f16eb5 Merge pull request #5219 from youknowone/0.3.1
0.3.1
2024-04-10 17:41:28 +09:00
Jeong YunWon
8816cd41d5 0.3.1 2024-04-10 17:39:57 +09:00
Jeong, YunWon
4b190eb412 Merge pull request #5221 from youknowone/update-indexmap
Update indexmap
2024-04-09 12:50:42 +09:00
Jeong YunWon
9f24841f6d remove -u from macOS/windows 2024-04-09 11:18:14 +09:00
Jeong YunWon
a0b6c36928 Update indexmap 2024-04-09 11:17:25 +09:00
Jeong, YunWon
3f691de7a3 Update chrono (#5220) 2024-04-09 04:13:39 +09:00
Jeong, YunWon
247572863a rustpython_vm::import::import_source (#5214)
* rustpython_vm::import::import_source

* always print exceptions when panic by expect_pyresult

if users want simple panic, Result::expect could be used.
2024-04-09 02:37:30 +09:00
Jeong, YunWon
5a5ac35f36 Update a few deps (#5217)
Co-authored-by: ognevnydemon <maksapple2306@gmail.com>
2024-04-09 02:12:15 +09:00
Jeong, YunWon
220594f263 always print exceptions when panic by expect_pyresult (#5215)
if users want simple panic, Result::expect could be used.
2024-04-09 01:04:27 +09:00
Jeong, YunWon
f541ca9dda Require cpu resource to test_search_anchor_at_beginning (#5218)
cherry-pick python/cpython#117616

Co-authored-by: CPython Developers <>
2024-04-09 01:01:46 +09:00
Jeong, YunWon
6a64af1066 Merge pull request #5213 from youknowone/vm-finalize
Add vm.finalize and enter/run docs
2024-04-08 01:43:48 +09:00
Jeong YunWon
35141266c1 vm.enter_and_expect 2024-04-08 00:43:17 +09:00
Jeong YunWon
6adb72727e Add vm.finalize and enter/run docs 2024-04-08 00:14:28 +09:00
Jeong, YunWon
6eed8f42c7 split vm.import and vm.import_from (#5212) 2024-04-08 00:07:38 +09:00
Jeong, YunWon
ae72316629 Merge pull request #5209 from Poppro/itertools-batched
Add itertools.batched Support
2024-04-06 19:26:02 +09:00
Jeong YunWon
90ab0e33a2 apply rustfmt 2024-04-06 19:15:15 +09:00
Hunter Harloff
9f7eb08fca Change to overflow error 2024-04-06 19:14:58 +09:00
Hunter Harloff
6a53ec3c5d Handle usize > usize::MAX case 2024-04-06 19:14:58 +09:00
Hunter Harloff
c5550cd466 Simplify Optional Syntax
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2024-04-06 19:14:58 +09:00
Hunter Harloff
f6569313d7 Move test_itertools to CPython 3.12 Spec 2024-04-06 19:14:58 +09:00
Hunter Harloff
880036c0ae CPython Compliance: throw ValueError before TypeError 2024-04-06 19:14:58 +09:00
Hunter Harloff
da53eeea57 Add itertools.batched Support 2024-04-06 19:14:58 +09:00
Jeong, YunWon
e349b0f18b Merge pull request #5125 from qingshi163/sre-dev
Update Sre Engine Implementing to CPython 3.12
2024-03-30 18:50:00 +09:00
Jeong, YunWon
34ffa93945 Remove commented out code 2024-03-30 18:49:33 +09:00
Jeong, YunWon
7be6308248 Merge pull request #5206 from dchiquito/fix-test-zipfile
Increase threshold for zipfile test_many_opens
2024-03-28 17:03:47 +09:00
Kangzhi Shi
57e7f4db95 bump string.py mark passed tests 2024-03-22 14:59:19 +09:00
Jeong YunWon
f5fbb5b06f replace sre-engine to rustpython-sre_engine 2024-03-22 14:59:19 +09:00
Kangzhi Shi
d9375b9fe1 impl re.template(),
template_compile template_expand subx
2024-03-22 14:59:19 +09:00
Kangzhi Shi
1e3d57817c Replace re_test.py from CPython 3.12 and mark failed tests 2024-03-22 14:59:09 +09:00
Kangzhi Shi
ebe555203a Replace Lib/sre_* from CPython 2024-03-22 14:54:02 +09:00
Kangzhi Shi
280337a305 Add Lib/re/* from CPython 3.12 2024-03-22 14:54:02 +09:00
Jeong, YunWon
02cec859e5 Merge pull request #5202 from youknowone/sre-engine
Import sre-engine repository to main RustPython
2024-03-22 14:53:25 +09:00
Jeong YunWon
1dd9a2fbe4 suppress clippy warnings 2024-03-22 11:28:49 +09:00
Daniel Chiquito
df363c0ba7 Skip typing test which causes other failures (#5207) 2024-03-22 10:26:40 +09:00
Daniel Chiquito
90724b32ec Implement new clippy lints (#5208)
* Implement new clippy lints

clippy was just updated and has a few minor issues with the code base.

* Forgotten lint hidden behind feature
2024-03-22 10:25:53 +09:00
Daniel Chiquito
0a24e106ba Increase threshold for zipfile test_many_opens
It turns out that there are many other tests that can impact
test_many_opens by leaving unclosed file handles. Rather than fix them
all, it is easier to simply increase the threshold for the problematic
test.
2024-03-21 13:37:46 -04:00
Daniel Chiquito
e6c73883ea Revert test skip 2024-03-21 13:36:28 -04:00
Daniel Chiquito
e315077630 Add TODO: RUSTPYTHON to skip reason 2024-03-21 11:31:03 -04:00
Daniel Chiquito
85c427b842 Reset exception in WithCleanupFinish (#5203)
Context managers have an `__exit__` function that returns a boolean-like
object. If the object is truthy, then exceptions are suppressed.

If an exception was thrown while resolving that boolean, it would leak
and live on in the error stack, getting tacked on to all future
exceptions. This caused several mysterious test failures which would
only trigger after this very specific event was tested in `test_with`.

The solution is to move a call to `vm.set_exception()` before
attempting the `try_to_bool()` which threw the error.

Minimal example to reproduce the bug:
```py
import sys
import traceback

class cm(object):
    def __init__(self):
        pass

    def __enter__(self):
        return 3

    def __exit__(self, a, b, c):
        class Bool:
            def __bool__(self):
                1 // 0
        return Bool()

try:
    with cm():
        raise Exception("Should NOT see this")
except ZeroDivisionError:
    print("exception caught, as expected")

print("There should now be no exception")
traceback.print_exc()
print(sys.exc_info())
```
2024-03-22 00:12:01 +09:00
Daniel Chiquito
5ee5531f32 Properly unload modules between tests (#5192)
There seems to have been a bug in the libregrtest code which unloaded
modules between tests. The previous state was calculated using
`sys.modules.keys()`, which is actually a mutable object that is updated
as the underlying `sys.modules` is updated. The result was that modules
were not unloaded between tests, which is the root cause for
`test_unittest` failing when run after `test_import` and
`test_importlib`.

This code is copied from 3.12. Ideally all of `libregrtest` should
probably be updated as it seems wildly out of date, but that's a lot
more work.
2024-03-21 21:51:57 +09:00
Jeong, YunWon
3737f2a091 make adding a single module simpler for interpreter users (#4792) 2024-03-21 21:48:29 +09:00
Daniel Chiquito
ac78517044 Skip TestScander.test_uninstantiable (#5204)
This test was marked as an expected failure. Because the garbage
collector is missing, that meant that the `os.scandir` object went
unclosed. This object was squatting on the file descriptors of all the
files contained in the test directory, which was breaking test_zipfile.
2024-03-21 14:44:03 +09:00
Jeong YunWon
12601d0b44 integrate sre_engine crate to workspace 2024-03-18 17:24:49 +09:00
Jeong YunWon
b3a606d9df Add 'vm/sre_engine/' from commit '21fc2059b70ebd5bf4a7c524c40e7d4347e065dc'
git-subtree-dir: vm/sre_engine
git-subtree-mainline: 426e582ba0
git-subtree-split: 21fc2059b7
2024-03-18 17:05:07 +09:00
Nikita Sobolev
426e582ba0 Remove incorrect @expectedFailures from test_cmd_line (#5201)
After you suggestion in https://github.com/python/cpython/issues/116504#issuecomment-1999239012 I went to take a look at `test_cmd_line` in RustPython (it was so long ago I contributed to this amazing project, so may thing had changed!), and I've noticed this.

This is a problem, here' the simplest demo:

```python
import unittest

class TestMe(unittest.TestCase):
    @unittest.expectedFailure
    def test_me(self):
        def run():
            raise ValueError

        with self.subTest(run=run):
            run()

if __name__ == '__main__':
    unittest.main()
```

This works as expected:

```
» ./python.exe ex.py
x
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK (expected failures=1)
```

This does not:

```python
import unittest

class TestMe(unittest.TestCase):
    def test_me(self):
        @unittest.expectedFailure
        def run():
            raise ValueError

        with self.subTest(run=run):
            run()

if __name__ == '__main__':
    unittest.main()
```

Produces:

```
» ./python.exe ex.py
E
======================================================================
ERROR: test_me (__main__.TestMe.test_me) (run=<function TestMe.test_me.<locals>.run at 0x1057a2150>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/sobolev/Desktop/cpython2/ex.py", line 10, in test_me
    run()
    ~~~^^
  File "/Users/sobolev/Desktop/cpython2/ex.py", line 7, in run
    raise ValueError
ValueError

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)
```

So, I propose to remove these decorators, let's only keep `TODO` comments to indicate separate failures.
2024-03-15 22:15:45 +09:00
Kirill Podoprigora
92c8b371ae Update colorsys.py and test_colorsys.py to 3.12 (#5198) 2024-03-13 15:22:57 +09:00
Kirill Podoprigora
d8f2bd04ac Update cgitb.py to 3.12 (#5197) 2024-03-13 15:22:24 +09:00
Kirill Podoprigora
855fa1411f Update ftplib and test_ftplib to 3.12 (#5196) 2024-03-13 07:35:16 +09:00
Kirill Podoprigora
4e7b3bc8f2 Update pprint.py and test_pprint.py to 3.12 (#5195) 2024-03-12 22:36:10 +09:00
Kirill Podoprigora
83d1ad8a2c Update test_operator.py to 3.12 (#5194) 2024-03-12 22:35:21 +09:00
Kirill Podoprigora
7f02324dce Update Lib/test/test_hmac.py to 3.12 version (#5188) 2024-03-11 22:04:35 +09:00
Jeong, YunWon
d453026d19 Merge pull request #5191 from dchiquito/fix-failing-tests
Fix convenient failing tests
2024-03-11 15:03:39 +09:00
wellweek
2fde8e91e5 fix some typos (#5187)
Signed-off-by: wellweek <xiezitai@outlook.com>
2024-03-11 15:01:37 +09:00
Daniel Chiquito
23ebbd021b Skip test_format.test_locale
I had previously `test_locale` as expected to fail, as it did indeed
fail on my system due to unimplemented functionality. As it happens, it
passes in CI because the locale settings used there (`C`, I believe)
just happen to format integers the same with "%d" as "%n".

I mistakenly un-marked it because I thought I misunderstood the problem.
2024-03-10 22:53:39 -04:00
Daniel Chiquito
9b974bda0d Re-enable test_format.test_locale
Technically speaking, my system was misconfigured, leading me to disable
the test in the first place.

`test_locale` calls `locale.setlocale(locale.LC_ALL, '')`, which reads
the value of the `LANG` environment variable and uses that to look up
and reset all the locale settings. My system has `LANG=en_US.UTF-8`,
which is apparently not what this test was expecting. If `LANG` is unset
or set to `C`, the test passes, as it does in CI.
2024-03-10 22:30:41 -04:00
Daniel Chiquito
de7e4e49da Disable broken test_socket.py tests
There are a substantial number of socket tests that are disabled due to
`bind(): bad family` errors. It seems like RustPython only supports a
small subset of the required connection families, so the failing tests
are broken for the same reasons.
2024-03-09 18:22:57 -05:00
Daniel Chiquito
ead42beff6 Disable test_locale in test_format.py
See https://github.com/RustPython/RustPython/issues/5181
2024-03-09 18:22:57 -05:00
Daniel Chiquito
35229721ea Fix test_cmd_line.py
The failing test was unsetting `PYTHONPATH`, but neglecting to unset
`RUSTPYTHONPATH`, which obviously was not significant for the original
CPython test. Including `RUSTPYTHONPATH` in the test fixes it.
2024-03-09 18:22:57 -05:00
rrupy
2f8e5189d3 Use ast::Suite::parse instead of deprecated parse_program. (#5186) 2024-03-05 22:37:42 +09:00
Jeong, YunWon
4c8cd67db9 Merge pull request #5176 from Blues-star/csv
Update csv.py and test_csv.py from CPython v3.12
2024-03-05 22:37:09 +09:00
Blues-star
54247df801 implement more csv features
Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2024-03-05 15:10:35 +09:00
Blues-star
e4be47a08b Mark failing tests as expectedFailure 2024-03-05 15:10:35 +09:00
Blues-star
d2bf69e354 Update test_csv.py from CPython v3.12.0 2024-03-05 15:10:35 +09:00
Blues-star
88ee64d585 Update csv.py from CPython v3.12.0 2024-03-05 15:10:35 +09:00
Jeong, YunWon
d0c827ed38 Merge pull request #5184 from youknowone/symboltable
better symboltable error message
2024-02-29 22:19:46 +09:00
Jeong YunWon
d26766b7bc better symboltable error message 2024-02-29 21:30:31 +09:00
Jeong, YunWon
e6c6f966f7 Merge pull request #5180 from dchiquito/3.12-collections
Update `_collections_abc.py` and `test_collections.py` to 3.12.2
2024-02-26 13:20:36 +09:00
Daniel Chiquito
407f251866 Un-skip passing typing test
I missed that the typing test I disabled was on a base test class.
Moving the expected failure to the subclass allows the passing test to
pass.
2024-02-24 13:33:17 -05:00
Daniel Chiquito
6cd9e54427 Update test_ordered_dict.py to 3.12.2 2024-02-24 12:00:07 -05:00
Daniel Chiquito
c9ec4507ad Disable broken test_repr in test_typing.py
This should be resolved when `typing.py` and `test_typing.py` are
updated to 3.12.
2024-02-24 11:58:26 -05:00
Daniel Chiquito
9481df23e1 Disable test_Buffer
This test will not work until the `__buffer__` and `__release_buffer__`
methods are implemented on the appropriate builtin types, which is
outside the current scope.
2024-02-24 11:02:41 -05:00
Daniel Chiquito
0bd8c2504c Update test_collections.py to 3.12.2 2024-02-23 20:16:16 -05:00
Daniel Chiquito
bcb35919a4 Update collections/__init__.py to 3.12.2 2024-02-23 20:14:41 -05:00
Daniel Chiquito
5dc6d5582a Update _collections_abc.py to 3.12.2 2024-02-23 20:13:41 -05:00
Jeong, YunWon
a8ab7dd388 Merge pull request #5175 from Blues-star/copy
Update `copy.py` and `test_copy.py` from CPython v3.12
2024-02-21 12:02:54 +09:00
Jeong, YunWon
7c722c23a5 Merge pull request #5174 from Blues-star/code
Update code.py and test_code.py from CPython v3.12
2024-02-21 12:02:05 +09:00
Blues-star
83b8c3a3fc Update test_copy.py from CPython v3.12.0 2024-02-20 19:38:58 +08:00
Blues-star
36a36b200d Update copy.py from CPython v3.12.0 2024-02-20 19:37:42 +08:00
Jeong, YunWon
d297e73a55 Merge pull request #5172 from Blues-star/main
Update `configparser.py` and `test_configparser.py` from CPython v3.12
2024-02-20 18:26:05 +09:00
Blues-star
572053df82 est_co_lnotab_is_deprecated, 'test_invalid_bytecode' and est_code_hash_uses_bytecode test exceptions, add TODO 2024-02-19 20:30:06 +08:00
Blues-star
def5661728 Update test_code.py from CPython v3.12.0 2024-02-19 20:13:59 +08:00
Blues-star
078cd0d88c Update code.py from CPython v3.12.0 2024-02-19 20:04:09 +08:00
Blues-star
d061837467 Update test_configparser.py from CPython v3.12.0 2024-02-19 19:25:17 +08:00
Blues-star
defc3ac7f1 Update configparser.py from CPython v3.12.0 2024-02-19 19:22:38 +08:00
Dmitry Erlikh
97a0705d2e Fix Windows CI (#5168)
* pin openssl version for windows CI

* use cargo vcpkg

* install openssl with vcpkg

* use Swatinem/rust-cache right after dtolnay/rust-toolchain

* cargo install --target-dir=target cargo-vcpkg
---------

Co-authored-by: Dmitry Erlikh <d.erlikh@smartrecruiters.com>
2024-02-19 02:36:18 +09:00
Jeong, YunWon
a88c2fe000 Fix miri test failure (#5170) 2024-02-19 00:39:47 +09:00
Jeong, YunWon
2a62ef7344 Merge pull request #5169 from Blues-star/main
Update `calendar.py` and `test_calendar.py`  from CPython v3.12
2024-02-19 00:30:15 +09:00
Blues-star
258342e1ca Update test_calendar.py from CPython v3.12.0 2024-02-18 15:36:45 +08:00
Blues-star
69e8e4be43 Update calendar.py from CPython v3.12.0 2024-02-18 15:33:01 +08:00
Jeong, YunWon
1c93c1630b Merge pull request #5164 from dchiquito/frozenset-hash
Use CPython hash algorithm for frozenset
2024-02-10 14:55:24 +09:00
Jeong, YunWon
ec9f2cedd1 Merge pull request #5165 from dchiquito/unused-collections-bool
Removed unused __bool__ methods
2024-02-10 12:00:55 +09:00
Jeong, YunWon
10055772d8 Merge pull request #5163 from dchiquito/fix-benches
Fix benchmarks by replacing rust-cpython with pyo3

- Replace `cpython` and `python3-sys` dependencies with `pyo3`.
- Add `html_report` feature to `criterion`, it will be required in a
  future release.
- Remove `anyhow`. It was unused and cargo cleaned it up automatically.
- Use `compile` and `exec` from within the CPython context to
compile and execute benchmarks. There's probably more overhead to that than
the internal API had, but that overhead should be consistent per
benchmark.
2024-02-10 11:49:17 +09:00
Ikko Eltociear Ashimine
61e40de32b Fix typo in slot.rs (#5162)
overriden -> overridden
2024-02-10 11:43:40 +09:00
Daniel Chiquito
074d228a7a Use correct TODO syntax
Co-authored-by: fanninpm <luxverdans@sbcglobal.net>
2024-02-09 21:41:45 -05:00
Daniel Chiquito
bf461cdebc Use CPython hash algorithm for frozenset
The original hash algorithm just XOR'd all the hashes of the elements of
the set, which is problematic. The CPython algorithm is required to pass
the tests.

- Replace `PyFrozenSet::hash` with CPython's algorithm
- Remove unused `hash_iter_unorded` functions
- Add `frozenset` benchmark
- Enable tests
- Lower performance expectations on effectiveness test
- Adjust `slot::hash_wrapper` so that it doesn't rehash the computed
  hash value in the process of converting PyInt to PyHash.
2024-02-09 21:02:40 -05:00
Daniel Chiquito
ffc52ef87c Removed unused __bool__ methods
Python does not define `list().__bool__`, `dict().__bool__`, and
`str().__bool__`, and some tests were failing because they were
defined.
I could not find any references to them and deleting them doesn't
seem to break anything.
2024-02-09 20:58:17 -05:00
Daniel Chiquito
ea1f72e92d Replace rust-cpython with pyo3 in benches
Update the actual benchmark harnesses.

Because the internal APIs previously used are no longer available, I
opted to use `compile` and `exec` from within the CPython context to
compile and execute code. There's probably more overhead to that than
the internal API had, but that overhead should be consistent per
benchmark.

If anyone cares about hyperoptimizing benchmarks then they can optimize
the harness as well.
2024-02-09 16:12:47 -05:00
Daniel Chiquito
9693ad9b11 Replace rust-cpython with pyo3 in benchmarks
The benchmarks have been broken since Python 3.10 deprecated the API
they were using to parse and execute CPython baselines. Since then
rust-cpython has been deprecated in favor of pyo3.

- Replace `cpython` and `python3-sys` with `pyo3`.
- Add `html_report` feature to `criterion`, it will be required in a
  future release.
- Remove `anyhow`. It was unused and cargo cleaned it up automatically.
2024-02-09 15:15:22 -05:00
kingiler
bdf228eb42 Fix bug in binascii uu encoding. Pass more related unit tests. (#5160)
* Fix bug in binascii, passes more unit tests.

* Pass more additional tests due to this PR.
2024-02-09 22:42:39 +09:00
Jeong, YunWon
c4b1cc69be Merge pull request #5158 from dchiquito/fix-int-rounding
Fix integer rounding
2024-02-09 13:22:36 +09:00
Daniel Chiquito
36b9219e32 Bump ahash from 0.8.3 to 0.8.7
When building the project for the Miri CI step, the latest nightly
version apparently conflicts with the older `ahash` 0.8.3. Updating to
0.8.7 fixed the build for me locally.
2024-02-07 18:40:28 -05:00
Daniel Chiquito
43d7c71a68 Fix integer rounding
The [docs](https://docs.python.org/3/library/functions.html#round)
specify that calling `round` with a negative precision removes
significant digits, so that `round(12345, -2) == 12300`. The
implementation was simply returning the original integer.

Additionally, `round(a, b)` is implemented as `(a / 10^b) * 10^b`, using
half-even rounding during the division.
2024-02-07 14:19:17 -05:00
Alin-Ioan Alexandru
3eda1cf3b4 Deprecation warning fix for __complex__ (#5152) 2024-01-25 14:54:06 +09:00
deantvv
6917b4c2ca os: ns_to_sec rounding (#5150)
In cpython, they use `_PyTime_ROUND_FLOOR` to read time.
But in RustPython, we use `[Duration::from_secs_f64](https://doc.rust-lang.org/std/time/struct.Duration.html#method.try_from_secs_f64)` to read time.

Therefore, RustPython isn't affected by the rounding issue in the way
that cpython does. We can safely ignore the `0.5*1e-9` bit in
`ns_to_sec` function.
2024-01-23 20:09:22 +09:00
Kangzhi Shi
21fc2059b7 improve: fix double count on _count 2024-01-17 16:06:30 +02:00
Kangzhi Shi
10e51ba689 improve: use adjust_cursor reduce double calc 2024-01-17 16:06:30 +02:00
Kangzhi Shi
c93ea30b3b improve use StringCursor replace index based position 2024-01-17 16:06:30 +02:00
Kangzhi Shi
118a00c012 refactor _count general case 2024-01-17 16:06:30 +02:00
Kangzhi Shi
f9b2d10c71 improve search at_beginning 2024-01-17 16:05:11 +02:00
Kangzhi Shi
2fe129252f improve ctx in _match lazy create stack vec 2024-01-17 16:05:11 +02:00
Kangzhi Shi
17e1152de6 fix assert not mark 2024-01-17 16:05:11 +02:00
Kangzhi Shi
454aa4b654 fix count not advance 2024-01-17 16:05:11 +02:00
Kangzhi Shi
169368b7f0 fix some clippy 2024-01-17 16:05:11 +02:00
Kangzhi Shi
41bdcfe221 bump version to 0.5.0 2024-01-17 16:05:11 +02:00
Kangzhi Shi
003c45dbff remove unneccesary INFO logic on main dispatch 2024-01-17 16:05:11 +02:00
Kangzhi Shi
9378497346 clearup 2024-01-17 16:05:11 +02:00
Kangzhi Shi
99ed744c57 impl atomic group & possessive repeat 2024-01-17 16:05:11 +02:00
Kangzhi Shi
a9ed7de28d fix _count general case 2024-01-17 16:05:11 +02:00
Kangzhi Shi
39c0106e87 update to cpython 3.12 op code 2024-01-17 16:05:11 +02:00
Kangzhi Shi
9070e12e0d refactor _match with nest loop 2024-01-17 16:05:11 +02:00
Kangzhi Shi
d73cc5f58c update version and dependency 2024-01-17 16:05:11 +02:00
Kangzhi Shi
a777d22a53 fix _count 2024-01-17 16:05:11 +02:00
kenny the :/
aaae566231 Raise error on power with negative number (#5143) 2024-01-12 21:16:53 +09:00
NakanoMiku
28f0fa48a4 Fix abc error messages (#5140)
Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
2024-01-11 17:48:56 +09:00
Jeong, YunWon
1983138c91 Merge pull request #5148 from coolreader18/upd-nix
Update nix and socket2
2024-01-10 03:12:13 +09:00
Jeong, YunWon
9cc571be95 Fix windows stdlib build 2024-01-09 20:53:58 +09:00
Noa
602015fca1 Update nix and socket2 2024-01-09 20:53:58 +09:00
Evan Rittenhouse
1ab133dae8 None.__ne__(None) should be NotImplemented (#5124) 2024-01-08 15:03:57 +09:00
Jeong, YunWon
317f449454 Merge pull request #5126 from RustPython/dependabot/cargo/openssl-0.10.60
Bump openssl from 0.10.55 to 0.10.60
2023-12-30 18:04:30 +09:00
Jeong, YunWon
32f662ae80 Bump openssl from 0.10.55 to 0.10.62 2023-12-30 17:14:08 +09:00
Jeong, YunWon
7236109d75 Merge pull request #5144 from youknowone/clippy
Fix 1.75 clippy warnings
2023-12-30 13:16:10 +09:00
Jeong YunWon
506c8a633e Fix redox and nightly 2023-12-30 12:53:57 +09:00
Jeong YunWon
a309cb5d2c Fix 1.75 clippy warnings 2023-12-30 11:48:40 +09:00
Jeong, YunWon
5001b2e572 Merge pull request #5142 from youknowone/windows-sys
Windows sys
2023-12-30 03:48:06 +09:00
Jeong, YunWon
756088e7fb more winapi to windows-sys 2023-12-29 00:40:04 +09:00
Jeong, YunWon
4729ca3af0 Drop winapi from rustpython-vm 2023-12-28 23:44:47 +09:00
Jeong, YunWon
cccfb08835 replace winbase winnt to windows-sys 2023-12-28 22:40:49 +09:00
Jeong, YunWon
d01909a524 replace handleapi to windows-sys 2023-12-28 22:28:49 +09:00
Jeong, YunWon
ee128eac7c replace errorhandling to windows-sys 2023-12-28 22:28:33 +09:00
Jeong, YunWon
6df9732965 replace wincon to windows-sys 2023-12-28 22:28:33 +09:00
Jeong, YunWon
8a84a479f1 remove processthreadsapi 2023-12-28 22:28:33 +09:00
Jeong, YunWon
7513017e21 replace sysinfoapi to windows-sys 2023-12-28 22:28:33 +09:00
Jeong, YunWon
adf0788895 bump up windows{-sys} 2023-12-28 22:28:33 +09:00
Jeong, YunWon
fc91cd8bc7 clean up winapi features (#5141) 2023-12-28 21:51:26 +09:00
Michał Moskal
459cad8407 update indexmap to 1.9.3 (#5128) 2023-12-28 13:08:08 +09:00
Jeong, YunWon
863a5b5a49 Merge pull request #5134 from NakanoMiku39/main
Update libraries and test files from CPython v3.12
2023-12-28 13:01:13 +09:00
NakanoMiku
1c6336b350 Merge branch 'RustPython:main' into main 2023-12-28 03:26:14 +08:00
Jeong, YunWon
fb12a4c219 Merge pull request #5136 from youknowone/fix-wasi-ci
Fix wasi CI
2023-12-28 02:50:30 +09:00
Jeong YunWon
16a3edd432 Fix wasi CI 2023-12-28 02:34:41 +09:00
Jeong, YunWon
ebc8f60e21 Merge pull request #5135 from youknowone/fix-malachite-version
Fix malachite-bigint version
2023-12-28 02:33:21 +09:00
Jeong YunWon
727f97fd48 Fix malachite-bigint version 2023-12-28 01:50:02 +09:00
NakanoMiku
de626b8627 Update test_file.py from CPython v3.12.0 2023-12-25 15:19:28 +08:00
NakanoMiku
f519ffdb18 Add asynchat.py and asyncore.py from CPython v3.12.0 2023-12-24 04:11:04 +08:00
NakanoMiku
57f9478d16 Add test_bz2.py from CPython v3.12.0 2023-12-24 04:07:12 +08:00
NakanoMiku
5700fa3953 Update argparse.py from CPython v3.12.0 2023-12-24 04:05:39 +08:00
NakanoMiku
44438bffbd Update argparse.py from CPython v3.12.0 2023-12-24 04:04:25 +08:00
NakanoMiku
dd0c393b45 Update test_bdb.py from CPython v3.12.0 2023-12-24 04:03:20 +08:00
NakanoMiku
a00a387735 Update bdb.py from CPython v3.12.0 2023-12-24 03:59:38 +08:00
NakanoMiku
b4ee044fa6 Update test_base64.py from CPython v3.12.0 2023-12-24 03:58:36 +08:00
NakanoMiku
df98264dd0 Update base64.py from CPython v3.12.0 2023-12-24 03:58:10 +08:00
NakanoMiku
b6c2179893 Update test_bisect.py from CPython v3.12.0 2023-12-24 03:56:28 +08:00
NakanoMiku
b5176fdbc0 Update bisect.py from CPython v3.12.0 2023-12-24 03:54:18 +08:00
NakanoMiku
79231ee399 Edit test_abc.py 2023-12-22 03:22:28 +08:00
NakanoMiku
c0f6a2f8c3 Update test_abc.py from CPython v3.12.0 2023-12-22 03:18:10 +08:00
NakanoMiku
99531514c8 Update abc.py from CPython v3.12.0 2023-12-22 03:15:47 +08:00
Jeong, YunWon
0fab6e6063 Merge pull request #5127 from NakanoMiku39/main
Update test files from CPython v3.12.0
2023-12-01 04:14:02 +09:00
NakanoMiku
784b5f1b1c Edit test_dictviews
ExpectedFailure added at line 282
2023-11-30 21:58:26 +08:00
NakanoMiku
3945e9a4ed Update test_epoll.py from CPython v3.12.0 2023-11-30 21:06:09 +08:00
NakanoMiku
ad3f0aecaf Update test_eof.py from CPython v3.12.0 2023-11-30 21:05:18 +08:00
NakanoMiku
1d76b762c7 Update test_eintr.py from CPython v3.12.0 2023-11-30 21:01:17 +08:00
NakanoMiku
f7c7398ba8 Update test_dynamic.py from CPython v3.12.0 2023-11-30 20:55:54 +08:00
NakanoMiku
186eac5095 Update test_dtrace.py from CPython v3.12.0 2023-11-30 20:55:02 +08:00
NakanoMiku
43ede611e3 Update test_dictviews.py from CPython v3.12.0 2023-11-30 20:53:15 +08:00
NakanoMiku
d5e4af7a4b Update test_dict.py from CPython v3.12.0 2023-11-30 20:51:40 +08:00
NakanoMiku
f1991c25bc Edit test_descrtut.py
ExpectedFailures at line 469-472
I'm not able to put a @unittest.expectedFailure for each tests so I commented them out
2023-11-30 20:48:58 +08:00
NakanoMiku
c4e2d6e379 Add test_descrtut.py from CPython v3.12.0 2023-11-30 20:46:29 +08:00
NakanoMiku
b11d554c11 Edit test_descr.py
Skip test at line 1861
ExpectedFailure at line 5104
2023-11-30 20:45:05 +08:00
NakanoMiku
b05d5920ab Update test_descr.py from CPython v3.12.0 2023-11-30 20:40:19 +08:00
NakanoMiku
926c4cef27 Update test_defaultdict.py from CPython v3.12.0 2023-11-30 20:27:46 +08:00
NakanoMiku
5c6b4cbd3c Update test_decorators.py from CPython v3.12.0 2023-11-30 20:27:09 +08:00
NakanoMiku
b93199f007 Update test_context.py from CPython v3.12.0 2023-11-30 20:11:24 +08:00
NakanoMiku
07f013dae2 Edit test_complex.py
ExpectedFailures added
2023-11-30 20:07:44 +08:00
NakanoMiku
fa3eae677d Update test_complex.py from CPython v3.12.0 2023-11-30 20:06:24 +08:00
NakanoMiku
c5364ca157 Update test_compare.py from CPython v3.12.0 2023-11-30 20:01:48 +08:00
NakanoMiku
c28cca97d1 Update test_code_module.py from CPython v3.12.0 2023-11-30 19:53:37 +08:00
NakanoMiku
9a61716f74 Edit test_cmd_line_script.py 2023-11-30 19:52:25 +08:00
NakanoMiku
700f2b9c12 Update test_cmd_line_script.py from CPython v3.12.0 2023-11-30 19:49:27 +08:00
NakanoMiku
999dcbdd1b Edit test_cmd_line.py 2023-11-30 19:48:22 +08:00
NakanoMiku
06bb68a6c6 Update test_cmd_line.py from CPython v3.12.0 2023-11-30 19:45:47 +08:00
ChenyG
dc4f6994fb Support slice hash (#5123)
* make slice object hashable

* Update test_slice.py from CPython v3.12

* remove TODO

* remove outdated tests
2023-11-25 13:11:17 +09:00
Jeong, YunWon
8fc2c39d62 Merge pull request #5122 from NakanoMiku39/main
Update test files from CPython v3.12.0
2023-11-23 23:10:36 +09:00
ChenyG
4d6a180638 Update ast, test_ast from CPython 3.12.0 (#5121) 2023-11-23 23:06:12 +09:00
NakanoMiku
460e9833f0 Edit Lib/test/test_cmath.py
ExpectedFailure added at line 628
2023-11-23 16:38:32 +08:00
NakanoMiku
c8256c5450 Update Lib/test/test_cmath.py from CPython v3.12.0 2023-11-23 16:36:59 +08:00
NakanoMiku
e06f5ccfe4 Update Lib/test/test_class.py from CPython v3.12.0 2023-11-23 16:31:27 +08:00
NakanoMiku
68dc5ca052 Edit Lib/test/test_c_locale_coercion.py
ExpectedFailure added at line 247
2023-11-23 16:21:42 +08:00
NakanoMiku
ac21576ab9 Add Lib/test/test_c_locale_coercion.py from CPython v3.12.0 2023-11-23 16:19:42 +08:00
NakanoMiku
b0ebe58636 Update Lib/test/test_bufio.py from CPython v3.12.0 2023-11-23 15:57:39 +08:00
NakanoMiku
7833fd9b12 Edit Lib/test/test_bool.py 2023-11-23 15:53:48 +08:00
NakanoMiku
81208637f5 Update Lib/test/test_bool.py from CPython v3.12.0 2023-11-23 15:51:02 +08:00
NakanoMiku
5303b33c8b Update Lib/test/test_bigmem.py from CPython v3.12.0 2023-11-23 15:42:40 +08:00
NakanoMiku
f54b80f88c Add Lib/test/test_bigaddrspace.py from CPython v3.12.0 2023-11-23 15:40:57 +08:00
Noa
068249196f Use new 1.74 features (#5118) 2023-11-17 13:01:05 +09:00
Noa
87cf891e50 Make PyMethodDef construction const (#5117)
* Make PyMethodDef construction const

* Remove iter_chain![]

Obsolete since arrays now impl IntoIterator
2023-11-15 12:52:47 +09:00
Jeong, YunWon
6d23daa480 Merge pull request #5115 from NakanoMiku39/main
Update test_opcodes.py, test_codeop.py and codeop.py from CPython v3.12
2023-11-10 01:45:00 -08:00
NakanoMiku39
ee9f6ae079 Edit Lib/test/test_codeop.py
cpython version: 3.12
2023-11-09 15:29:35 +08:00
NakanoMiku39
488bac7413 Update codeop.py from CPython v3.12 2023-11-09 15:20:52 +08:00
NakanoMiku39
8ff0872d41 Update test_codeop.py from CPython v3.12 2023-11-09 15:11:46 +08:00
NakanoMiku39
a96926cd77 Remove Lib/test/test_opcode.py
This file is a duplicate of test_codeop.py with a few changes
2023-11-09 15:06:08 +08:00
NakanoMiku39
06b9b4938d Add Lib/test/typinganndata folder
cpython version: 3.12
2023-11-09 15:05:04 +08:00
NakanoMiku39
b5e21ab136 Add Lib/test/test_opcodes.py
cpython version: 3.12
2023-11-09 14:59:59 +08:00
NakanoMiku
fe617431f4 Update test__locale.py and test_atexit.py from CPython v3.12 (#5114)
* Update test__locale.py from CPython v3.12

* Update Cargo.lock

* Update test_atexit.py from CPython v3.12
2023-11-08 22:39:56 -08:00
NakanoMiku39
99d992f708 Update test_atexit.py from CPython v3.12 2023-11-08 20:41:23 +08:00
NakanoMiku39
f682d184fb Update Cargo.lock 2023-11-08 20:18:46 +08:00
NakanoMiku39
2bfbfd7a03 Update test__locale.py from CPython v3.12 2023-11-08 19:25:01 +08:00
NakanoMiku
c32369bd27 Add Lib/test/test___all__.py (#5110)
* Add Lib/test/test___all__.py

cpython version: 3.12

* Edit Lib/test/test___all__.py

Add @unittest.expectedFailure for function test_all(self)

cpython version: 3.12
2023-11-07 23:37:26 -08:00
Steve Shi
b8f22e2967 bump malachite to 0.4.4 (#5069) 2023-11-07 22:43:49 -08:00
Tae-Geun Kim
b4b71e5a11 Bump puruspe to 0.2 (#5112) 2023-11-07 22:37:06 -08:00
Olivier Lemasle
4dacbc51e2 Remove outdated info about yanked crates (#5111)
The `rustpython-*` crates have been published since this message was added.
2023-11-06 21:12:21 -08:00
Jeong, YunWon
af884cb284 First step for Python 3.12 support (#5078)
* Mark 3.12

* Update importlib from Python 3.12.0

* Update test_importlib from Python3.12

* Mark failings tests from importlib

* Update test.support from Python3.12

* Fix unsupported parser feature

* mark failing test

* Update functools from Python 3.12

* manual type annotation

* slice behavior changed in 3.12

* empty unittest.main returns non-zero

* test_decimal from CPython 3.12

* Mark failing tests

* Update test_unicode from CPython 3.12

* Update test_functools from Python 3.12

* Update enum from Python 3.12

* enum

* Doc format changed

* Update test_module from CPython

---------

Co-authored-by: CPython developers <>
2023-10-22 19:19:05 -07:00
Jeong, YunWon
a75f26b922 Fake PEP-0695 with empty __type_params__ (#5098) 2023-10-22 18:01:06 -07:00
Jonas Zaddach
d32cb7efdd Add missing 'sysinfoapi' feature for winapi (#5094) 2023-10-19 18:04:11 -07:00
Dan Näsman
03576615e4 fix type_check (#5097) 2023-10-19 18:03:34 -07:00
Jeong, YunWon
bda7c5cf06 Fix examples/package_embed (#5096) 2023-10-18 15:27:51 -07:00
Dan Näsman
0e72ba8819 implement PyObject_Type and PyObject_TypeCheck (#5091) 2023-10-17 16:22:10 -07:00
Dan Näsman
4e6172b99d Add object protocol correspoinding to PyObject_GetAIter (#5090) 2023-10-16 10:39:29 -07:00
Amuthan Mannar
9241e2e5d5 [VM] Object pickling implementation for product object (python itertools) (#5089)
* Implemented __reduce__, __setstate__ in product object
2023-10-12 20:12:33 +09:00
Dan Näsman
830389f62c implement dir for ByObjectRef (#5088) 2023-10-09 15:16:38 +09:00
Jeong, YunWon
2c3dbb5eed Merge pull request #5081 from MannarAmuthan/bool-compare
Implemented compare operation for boolean types in JIT engine
2023-10-07 22:03:50 +09:00
Jeong, YunWon
f1b36233b6 Merge pull request #5086 from MannarAmuthan/float-int-ops
[JIT]  Binary operations for integers and floating-point numbers allowing mixed type calculations
2023-10-07 21:15:04 +09:00
Amuthan Mannar
eb83b729b2 Formatted code files 2023-10-07 21:05:27 +09:00
Amuthan Mannar
b69e6a910d Added missed tests in int_tests of jit 2023-10-07 21:05:27 +09:00
Amuthan Mannar
b9ee0b6b7a Implemented compare operation for boolean and int types 2023-10-07 21:05:27 +09:00
Amuthan Mannar
f8365ca6c3 Implemented compare operation for boolean types in JIT engine 2023-10-07 21:05:27 +09:00
Amuthan Mannar
c2f159b24a Formatted code files 2023-10-07 12:34:32 +05:30
Amuthan Mannar
54d5869457 Implement binary operations for integers and floating-point numbers, allowing mixed type calculations 2023-10-07 12:02:18 +05:30
Jeong YunWon
285ba765a7 0.4.2 with dependency update 2023-10-07 14:41:32 +09:00
Jeong, YunWon
91c0c8b002 Merge pull request #5085 from youknowone/windows
port winapi to windows-sys
2023-10-07 14:14:51 +09:00
Jeong, YunWon
4a61eba58e rustpython_vm::windows 2023-10-07 03:02:56 +09:00
Jeong, YunWon
58df09b492 GetLastError 2023-10-07 03:02:56 +09:00
Yaminyam
6313e4c9fb windows-sys attrs
Copied from https://github.com/RustPython/RustPython/pull/4086
2023-10-07 03:02:56 +09:00
Jeong, YunWon
0f3a9311e0 port winbase 2023-10-07 03:02:19 +09:00
Jeong, YunWon
987d50c092 port to windows-rs (#5080)
* Fix OpenSSL in windows CI

* bump windows-rs

* prepare windows-sys 0.48

* CloseHandle

* DuplicateHandle

* CreatePipe

* GetFileType

* GetExitCodeProcess

* TerminateProcess

* GetStdHandle

* GetCurrentProcess

* DeleteProcThreadAttributeList

* WaitForSingleObject

* CreateProcessW

* InitializeProcThreadAttributeList

* UpdateProcThreadAttribute

* clean up helpers
2023-10-07 03:01:42 +09:00
Jeong, YunWon
2fa88f94b6 Skip flaky test_enum::test_unique_composite (#5084) 2023-10-06 14:34:50 +09:00
Jeong, YunWon
7022512b83 retry windows ci openssl fix (#5082) 2023-10-06 14:34:35 +09:00
Jeong, YunWon
4135da42ac Fix clippy (#5083)
* Fix clippy

* Fix nightly clippy
2023-10-06 03:17:03 +09:00
Jeong, YunWon
d975c51b96 implement more warnings (#5077) 2023-10-04 23:42:37 +09:00
Jeong, YunWon
23bf5c42ca bump up and sync dependencies with rustpython-parser (#5075) 2023-10-04 23:42:07 +09:00
Jeong, YunWon
c3a8d5a9b5 Retry to fix win_lib_path again (#5076) 2023-10-03 23:19:31 +09:00
dvermd
0d0139b322 Update enum to CPython 3.11.5 (#5074)
part of: #4564
2023-10-03 22:32:38 +09:00
Jeong, YunWon
5384a4766a Merge pull request #5073 from dvermd/ftplib_311
Update ftplib to CPython 3.11.5
2023-10-01 20:51:26 +09:00
Jeong, YunWon
a12233e788 Merge pull request #5072 from dvermd/fractions_311
Update fractions to CPython 3.11.5
2023-10-01 20:50:57 +09:00
Jeong, YunWon
80e7186aa9 Merge pull request #5071 from dvermd/fileinput_311
Update fileinput to CPython 3.11.5
2023-10-01 20:50:33 +09:00
Jeong, YunWon
9ea4baa41c Merge pull request #5070 from dvermd/compileall_311
Update compileall to CPython 3.11.5
2023-10-01 20:50:08 +09:00
dvermd
0a76a9b115 Update ftplib to CPython 3.11.5
part_of: #4564
2023-10-01 07:12:00 +02:00
dvermd
fd98ab2084 Update fractions to CPython 3.11.5 2023-10-01 07:06:11 +02:00
dvermd
48d4c22362 Update fileinput to CPython 3.11.5 2023-10-01 07:04:53 +02:00
dvermd
10e4f715a5 Update compileall to CPython 3.11.5 2023-10-01 06:59:49 +02:00
Jelmer Vernooij
9031a0ac9f bump lz4_flex dependency to 0.11 (#5067) 2023-09-27 17:43:17 +09:00
Jeong, YunWon
37ce45fffb Fix windows CI error (#5068) 2023-09-27 17:42:54 +09:00
Jelmer Vernooij
39169de63a bump is-macro to 0.3 (#5066) 2023-09-23 01:06:47 +09:00
Caleb Cartwright
21cff29c31 ci: simplify rustfmt invocation (#5064) 2023-09-14 17:11:44 +09:00
Jeong, YunWon
589b3eb7c3 Merge pull request #5052 from youknowone/update-parser
Update parser & Release 0.3.0
2023-09-07 00:27:20 +09:00
Kangzhi Shi
4e6b27144a introduce SearchIter 2022-08-15 21:35:08 +02:00
Kangzhi Shi
26a78dbaa4 update to 0.4.1 2022-08-15 21:33:51 +02:00
Kangzhi Shi
7e7b973481 clearup 2022-08-15 21:33:51 +02:00
Kangzhi Shi
646c8ac657 impl opinfo charset 2022-08-15 21:33:51 +02:00
Kangzhi Shi
236631141f impl opinfo literal 2022-08-15 21:33:51 +02:00
Kangzhi Shi
c4f10edc95 impl opinfo single literal 2022-08-15 21:33:51 +02:00
Kangzhi Shi
e42df1d859 update version to 0.4.0 2022-08-15 21:33:51 +02:00
Kangzhi Shi
18258000cd clearup 2022-08-15 21:33:51 +02:00
Kangzhi Shi
c494feb7f7 refactor split Marks 2022-08-15 21:33:51 +02:00
Kangzhi Shi
de8973d77a simplify lifetime 2022-08-15 21:33:51 +02:00
Kangzhi Shi
942063d4ef refactor benches 2022-08-15 21:33:51 +02:00
Kangzhi Shi
c15387e972 refactor tests 2022-08-15 21:33:51 +02:00
Kangzhi Shi
c31462d51b refactor split State with Request 2022-08-15 21:33:51 +02:00
Kangzhi Shi
8b1fcea7ec update version to 0.3.1 2022-08-15 21:33:51 +02:00
Kangzhi Shi
a48f5b07c5 impl op_info 2022-08-15 21:33:51 +02:00
Kangzhi Shi
ca20b5951d update version to 0.3.0 2022-08-07 08:02:49 +02:00
Kangzhi Shi
f3b30443aa fix lifetime 2022-08-07 08:02:49 +02:00
Kangzhi Shi
ccae898885 fix next_ctx bug 2022-08-07 08:02:49 +02:00
Kangzhi Shi
982d8f53f2 fix next_ctx 2022-08-07 08:02:49 +02:00
Kangzhi Shi
34bde45a2c pass compile 2022-08-07 08:02:49 +02:00
Kangzhi Shi
9058f28788 refactor trait StrDrive instead enum 2022-08-07 08:02:49 +02:00
Kangzhi Shi
bf57f289bf update version to 0.2.1 2022-07-29 10:54:20 +02:00
Kangzhi Shi
4007f82765 optimize max_until and min_until 2022-07-29 10:54:20 +02:00
Steve Shi
919e1d7933 Refactor and fix multiple max_until recusion (#10)
* wip refactor engine

* wip 2 refactor engine

* wip 3 refactor engine

* wip 3 refactor engine

* wip 4 refactor engine

* wip 5 refactor engine

* refactor seperate Stacks

* fix clippy

* fix pymatch and search restore _stacks

* fix toplevel

* fix marks panic

* fix double max_until repeat context

* clearup

* update version to 0.2.0
2022-07-26 20:38:03 +02:00
Kangzhi Shi
74ebdaf4e8 fix panic OpMinUntil return before restore repeat 2022-07-11 21:30:48 +02:00
Noah
c5871f4c2a Merge pull request #9 from qingshi163/master
Add benchmark and replace hashmap to btreemap
2021-05-01 13:34:35 -05:00
Kangzhi Shi
58981a41e9 optimize; replace hashmap with btreemap 2021-04-22 19:27:24 +02:00
Kangzhi Shi
86435b8a4b add benchmark 2021-04-22 17:15:03 +02:00
Steve Shi
980863366c Merge pull request #8 from qingshi163/master
optimize opcode with once execution and search with cached offset
2021-04-22 08:40:02 +02:00
Kangzhi Shi
7324feef89 optimize search cache the string offset 2021-04-21 11:13:58 +02:00
Kangzhi Shi
5bd6b672d0 optimize opcode that execute only once 2021-04-21 11:09:10 +02:00
Steve Shi
869d80a34b Merge pull request #7 from qingshi163/master
optimize count
2021-04-21 09:34:21 +02:00
Kangzhi Shi
a3c3573d67 optimize count 2021-04-21 09:30:54 +02:00
Steve Shi
b4f0b05455 Merge pull request #6 from qingshi163/zerowidth
fix zerowidth search
2021-04-20 15:17:15 +02:00
Kangzhi Shi
df8453d387 fix zerowidth search 2021-04-20 10:19:27 +02:00
Noah
73abbace85 Add explicit include for Cargo files 2021-04-16 10:54:49 -05:00
Noah
d2b48fdea2 Release 0.1.1
sre-engine@0.1.1

Generated by cargo-workspaces
2021-04-16 10:40:42 -05:00
Noah
caef94d851 Merge pull request #5 from qingshi163/master
fix test_string_boundaries
2021-04-16 07:46:35 -05:00
Kangzhi Shi
9728dd8699 fix test_string_boundaries 2021-04-16 09:35:11 +02:00
Noah
b497b2234d Add more info to Cargo.toml 2021-04-05 11:10:32 -05:00
Noah
211a66cf33 Merge pull request #4 from RustPython/codegen-inline
Have generate_tests.py generate Patterns inline in tests.rs
2021-04-05 10:59:54 -05:00
Noah
ca1346ee03 Have generate_tests.py generate Patterns inline in tests.rs 2021-04-01 22:05:45 -05:00
Noah
2a43d66e11 Fix clippy lint 2021-04-01 21:38:20 -05:00
Noah
65c477414b Merge pull request #3 from qingshi163/master
fix OpAssert positive lookbehind
2021-02-03 09:06:37 -06:00
Kangzhi Shi
39a648bce1 fix OpAssert positive lookbehind 2021-02-03 13:32:48 +02:00
Noah
11532c5be9 Merge pull request #2 from RustPython/gen-tests
Compile regex patterns for tests with a script
2021-02-02 07:32:14 -06:00
Noah
cc4441b50f Compile regex patterns for tests with a script 2021-02-01 14:27:11 -06:00
Noah
174c9326a4 Merge pull request #1 from qingshi163/master
add tests; fix OpAssert panic
2021-02-01 14:20:39 -06:00
Kangzhi Shi
2473c3e49f add tests; fix OpAssert panic 2021-02-01 20:25:45 +02:00
Noah
2592067f95 Add LICENSE 2021-01-27 19:30:31 -06:00
Noah
9c95994dab Modify to work outside of rustpython-vm 2021-01-27 19:30:28 -06:00
Noah
db5bd646b9 Initial commit to switch to new repo 2021-01-27 19:29:55 -06:00
Kangzhi Shi
4416158ece fix multiple bugs; pass tests 2021-01-22 16:40:33 +02:00
Kangzhi Shi
7f0dad7901 fix back_peek_char 2021-01-21 19:24:10 +02:00
Kangzhi Shi
6a792324b0 fix at_beginning 2021-01-21 16:14:23 +02:00
Kangzhi Shi
f2311b56fc fix op branch 2021-01-21 12:48:32 +02:00
Kangzhi Shi
84113cba2c fix zero width repeat 2021-01-20 16:33:30 +02:00
Kangzhi Shi
97000fc4e0 fix multiple bugs; skip crash tests 2021-01-20 08:29:58 +02:00
Kangzhi Shi
13a8b6cc4e add bytes support and refactor 2021-01-20 08:29:58 +02:00
Kangzhi Shi
76c95abbb5 impl Match.lastgroup 2021-01-20 08:29:58 +02:00
Kangzhi Shi
33ef823645 impl Match.groupdict 2021-01-20 08:29:57 +02:00
Kangzhi Shi
817eb66167 fix OpMinUntil 2021-01-20 08:29:57 +02:00
Kangzhi Shi
f05f6cb44d impl Pattern.sub 2021-01-20 08:29:57 +02:00
Kangzhi Shi
36433a9f4d fix Opcode::BIGCHARSET 2021-01-20 08:29:57 +02:00
Kangzhi Shi
db84f32981 fix Opcode::CHARSET 2021-01-20 08:29:57 +02:00
Kangzhi Shi
af1a53cb05 impl Match.groups() 2021-01-20 08:29:57 +02:00
Kangzhi Shi
8fba935bba OpMaxUntil zero-width protection 2021-01-20 08:29:57 +02:00
Kangzhi Shi
8c442f599b rework OpMaxUntil; restruct popping context; add tests; 2021-01-20 08:29:57 +02:00
Kangzhi Shi
04bb80f157 impl Match.group 2021-01-20 08:29:57 +02:00
Kangzhi Shi
f7287553e9 impl re.Match object 2021-01-20 08:29:57 +02:00
Kangzhi Shi
ae44580371 general case for count 2021-01-20 08:29:57 +02:00
Kangzhi Shi
fa2adaf2ff Impl OpRepeatONe 2021-01-20 08:29:57 +02:00
Kangzhi Shi
af7901dcb2 impl OpMinUntil 2021-01-20 08:29:57 +02:00
Kangzhi Shi
5a4459856c impl OpRepeat 2021-01-20 08:29:57 +02:00
Kangzhi Shi
93c2b8b555 impl OpBranch 2021-01-20 08:29:57 +02:00
Kangzhi Shi
0b2c8d1fa2 impl OpMaxUntil 2021-01-20 08:29:57 +02:00
Kangzhi Shi
312e5b8756 impl opcode groupref and assert_not 2021-01-20 08:29:57 +02:00
Kangzhi Shi
aa0f20b93e impl Pattern.fullmatch, Pattern.search 2021-01-20 08:29:57 +02:00
Kangzhi Shi
78485fd8df create _sre.Match 2021-01-20 08:29:57 +02:00
Kangzhi Shi
4e03fb361f upgrade sre_parse.py; impl marks count 2021-01-20 08:29:56 +02:00
Kangzhi Shi
82922bf0d7 upgrade re version; implement helper functions; 2021-01-20 08:29:56 +02:00
Kangzhi Shi
e1362ead3c WIP structure 2021-01-20 08:29:56 +02:00
Noah
c2ee9ca3e0 WIP - native _sre 2021-01-20 08:29:56 +02:00
812 changed files with 98034 additions and 30102 deletions

View File

@@ -52,6 +52,7 @@
"metas", "metas",
"modpow", "modpow",
"nanos", "nanos",
"objclass",
"peekable", "peekable",
"powc", "powc",
"powf", "powf",
@@ -171,6 +172,8 @@
"scproxy", "scproxy",
"setattro", "setattro",
"setcomp", "setcomp",
"showwarnmsg",
"warnmsg",
"stacklevel", "stacklevel",
"subclasscheck", "subclasscheck",
"subclasshook", "subclasshook",

View File

@@ -1,6 +1,4 @@
{ {
"image": "mcr.microsoft.com/devcontainers/universal:2", "image": "mcr.microsoft.com/devcontainers/base:jammy",
"features": { "onCreateCommand": "curl https://sh.rustup.rs -sSf | sh -s -- -y"
"ghcr.io/devcontainers/features/rust:1": {} }
}
}

1
.gitattributes vendored
View File

@@ -4,3 +4,4 @@ Cargo.lock linguist-generated -merge
vm/src/stdlib/ast/gen.rs linguist-generated -merge vm/src/stdlib/ast/gen.rs linguist-generated -merge
Lib/*.py text working-tree-encoding=UTF-8 eol=LF Lib/*.py text working-tree-encoding=UTF-8 eol=LF
**/*.rs text working-tree-encoding=UTF-8 eol=LF **/*.rs text working-tree-encoding=UTF-8 eol=LF
*.pck binary

13
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# Keep GitHub Actions up to date with GitHub's Dependabot...
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
version: 2
updates:
- package-ecosystem: github-actions
directory: /
groups:
github-actions:
patterns:
- "*" # Group all Actions updates into a single larger pull request
schedule:
interval: weekly

304
.github/scripts/code_review.py vendored Normal file
View File

@@ -0,0 +1,304 @@
"""Code Reviewer for Gitea."""
import asyncio
import fnmatch
import json
import os
import re
from typing import Any
import requests
import aiohttp
from model import Model
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN", "")
HEADERS = {"Authorization": f"token {ACCESS_TOKEN}"}
GITHUB_EVENT_PATH = os.getenv("GITHUB_EVENT_PATH")
try:
with open(GITHUB_EVENT_PATH, "r") as f:
EVENT_DATA = json.load(f)
except FileNotFoundError:
print("Failed to load event data.")
exit(1)
FULL_CONTEXT_MODEL_NAME = os.getenv("FULL_CONTEXT_MODEL", "")
SINGLE_CHUNK_MODEL_NAME = os.getenv("SINGLE_CHUNK_MODEL", "")
FULL_CONTEXT_API_KEY = os.getenv("FULL_CONTEXT_API_KEY", "")
SINGLE_CHUNK_API_KEY = os.getenv("SINGLE_CHUNK_API_KEY", "")
EXCLUDE_PATTERNS = os.getenv("EXCLUDE", "").split(",")
def get_diff() -> str | None:
"""Get code difference between base and head from Gitea.
Returns:
str | None: code difference between base and head, or None if failed to get diff
"""
url = EVENT_DATA["pull_request"]["diff_url"]
try:
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.text
except requests.RequestException as e:
print(f"Failed to get diff: {e}")
return None
def parse_diff(diff: str) -> list[dict[str, Any]]:
"""Parse diff into list of dicts.
Args:
diff: str, code difference between base and head
Returns:
list[dict[str, Any]]: list of dicts, each dict represents a code chunks
"""
file_pattern = re.compile(
r"(?s)diff --git a/(.+?) b/(.*?)\r?\n(.*?)(?=diff --git a/|$)", re.S
)
old_new_pattern = re.compile(r"(?m)^(---|\+\+\+)\s+(.*)$")
chunk_range_pattern = re.compile(
r"@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*?)?(?=@@|\Z)",
re.MULTILINE | re.DOTALL,
)
list_diff = []
for match in file_pattern.finditer(diff):
diff_text = match.group(3)
old_new_match = list(old_new_pattern.finditer(diff_text))
if len(old_new_match) != 2:
continue
old_file = old_new_match[0].group(2)
old_file = old_file.lstrip("a/") if old_file.startswith("a/") else old_file
new_file = old_new_match[1].group(2)
if new_file == "/dev/null":
print("Neglict deleted file")
continue
new_file = new_file.lstrip("b/")
if any(fnmatch.fnmatch(new_file, pattern) for pattern in EXCLUDE_PATTERNS):
print(f"Exclude file {new_file}")
continue
output_diff_text = []
for chunk_range_match in chunk_range_pattern.finditer(diff_text):
old_idx = int(chunk_range_match.group(1))
new_idx = int(chunk_range_match.group(3))
for line in chunk_range_match.group(5).splitlines():
if line.startswith("-"):
output_diff_text.append(f"{old_idx} None {line}")
old_idx += 1
elif line.startswith("+"):
output_diff_text.append(f"None {new_idx} {line}")
new_idx += 1
else:
output_diff_text.append(f"{old_idx} {new_idx} {line}")
old_idx += 1
new_idx += 1
output_diff_text = "\n".join(output_diff_text)
list_diff.append(
{
"file": new_file,
"chunk": output_diff_text,
}
)
return list_diff
def create_comment(
file: str, ai_response: list[dict[str, Any]]
) -> list[dict[str, Any]]:
"""Create comments for single chunk review.
Args:
file: str, file name
ai_response: list[dict[str, Any]], AI response for single chunk review
Returns:
list[dict[str, Any]]: comments for single chunk review
"""
comments = []
for ai_response in ai_response:
comments.append(
{
"body": f"[REVIEW] {ai_response['reviewComment']}",
"path": file,
"new_position": int(ai_response["lineNumber"]),
}
)
return comments
async def analyze_single_chunks(
single_chunk_model: Model, parsed_diff: list[dict[str, Any]]
) -> list[dict[str, Any]]:
"""Analyze single chunks and create comments.
Args:
single_chunk_model: AI Session for single chunk analysis
parsed_diff: list[dict[str, Any]], parsed diff
Returns:
list[dict[str, Any]]: comments for single chunk review
"""
async def process_single_chunk(diff: dict[str, Any]):
file = diff["file"]
chunk = diff["chunk"]
response = await single_chunk_model.get_response_single_chunk(
file, title, description, chunk
)
response = response.strip("`").lstrip("json").strip() or "[]"
try:
response_json = json.loads(response)
return create_comment(file, response_json)
except json.JSONDecodeError:
print(f"Failed to parse response: {response}")
return []
title = EVENT_DATA["pull_request"]["title"]
description = EVENT_DATA["pull_request"]["body"]
tasks = [process_single_chunk(diff) for diff in parsed_diff]
results = await asyncio.gather(*tasks)
# Flatten the list of comments
comments = [comment for result in results for comment in result]
return comments
async def get_file_content(file: str) -> str | None:
"""Get file content from Gitea.
Args:
file: str, file name
Returns:
str | None: file content, or None if failed to get file content
"""
repo_url = EVENT_DATA["pull_request"]["head"]["repo"]["url"]
branch = EVENT_DATA["pull_request"]["head"]["ref"]
replaced_file = file.replace("/", "%2F")
url = f"{repo_url}/raw/{branch}%2F{replaced_file}?ref={branch}"
try:
async with aiohttp.ClientSession(headers=HEADERS) as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.text()
except aiohttp.ClientError as e: # More specific exception handling
print(f"Network error fetching {file}: {e}")
except asyncio.TimeoutError:
print(f"Timeout fetching {file}")
return None
async def analyze_full_context(
full_context_model: Model, parsed_diff: list[dict[str, Any]]
) -> str:
"""Analyze full context and create review.
Args:
full_context_model: AI Session for full context analysis
parsed_diff: list[dict[str, Any]], parsed diff
Returns:
str: review for full context
"""
async def get_file_data(diff: dict[str, Any]):
file = diff["file"]
chunk = diff["chunk"]
content = get_file_content(file)
if content is None:
return None
return f"File: {file}\n{content}\nDiff: {chunk}"
tasks = [get_file_data(diff) for diff in parsed_diff]
file_contents_list = await asyncio.gather(*tasks)
file_contents = [item for item in file_contents_list if item is not None]
if not file_contents:
return ""
title = EVENT_DATA["pull_request"]["title"]
description = EVENT_DATA["pull_request"]["body"]
response = await full_context_model.get_response_full_context(
title, description, file_contents
)
response = response.strip("`").lstrip("markdown").strip()
return response
def post_review(
full_context_review: str, single_chunk_comments: list[dict[str, Any]]
) -> None:
"""Post review to Gitea.
Args:
full_context_review: str, review for full context
single_chunk_comments: list[dict[str, Any]], comments for single chunk review
"""
repo_url = EVENT_DATA["pull_request"]["head"]["repo"]["url"]
pull_number = EVENT_DATA["number"]
commit_id = EVENT_DATA["pull_request"]["head"]["sha"]
url = f"{repo_url}/pulls/{pull_number}/reviews"
data = {
"body": full_context_review,
"event": "COMMENT",
"comments": single_chunk_comments,
"commit_id": commit_id,
}
response = requests.post(url, headers=HEADERS, json=data)
response.raise_for_status()
async def main() -> None:
"""Code Reviewer for Gitea: Asynchronous version."""
if EVENT_DATA["action"] not in ["opened", "synchronized"]:
print("Unsupported event.")
return
diff = get_diff()
if diff is None:
return
elif not diff:
print("No diff found.")
return
full_context_model = Model(
model=FULL_CONTEXT_MODEL_NAME,
api_key=FULL_CONTEXT_API_KEY,
is_full_context=True,
)
single_chunk_model = Model(
model=SINGLE_CHUNK_MODEL_NAME,
api_key=SINGLE_CHUNK_API_KEY,
is_full_context=False,
)
parsed_diff = parse_diff(diff)
comments_task = asyncio.create_task(
analyze_single_chunks(single_chunk_model, parsed_diff)
)
if EVENT_DATA["action"] == "opened":
full_context_response_task = asyncio.create_task(
analyze_full_context(full_context_model, parsed_diff)
)
full_context_response = await full_context_response_task
else:
full_context_response = ""
comments = await comments_task
post_review(full_context_response, comments)
if __name__ == "__main__":
asyncio.run(main())

261
.github/scripts/model.py vendored Normal file
View File

@@ -0,0 +1,261 @@
"""Model for code review."""
from enum import Enum
from typing import Any
import google.generativeai as genai
import typing_extensions as typing
from anthropic import AsyncAnthropic
from openai import AsyncOpenAI
class GoogleResponse(typing.TypedDict):
"""The response from Google model."""
lineNumber: int
reviewComment: str
class ModelProvider(Enum):
"""The model provider."""
OPENAI = "openai"
ANTHROPIC = "anthropic"
GOOGLE = "google"
DEEPSEEK = "deepseek"
@classmethod
def from_model(cls, model: str) -> "ModelProvider":
"""Get the model provider from the model name.
Args:
model (str): The model name.
Returns:
ModelProvider: The model provider.
"""
for prefix, provider in PREFIX_TO_MODEL.items():
if model.startswith(prefix):
return provider
raise ValueError(f"Unknown model: {model}")
PREFIX_TO_MODEL = {
"gpt": ModelProvider.OPENAI,
"o1": ModelProvider.OPENAI,
"o3": ModelProvider.OPENAI,
"claude": ModelProvider.ANTHROPIC,
"gemini": ModelProvider.GOOGLE,
"deepseek": ModelProvider.DEEPSEEK,
}
class Model:
"""The model class.
Attributes:
model (str): The model name.
api_key (str): The API key.
system_prompt (str): The system prompt.
max_tokens (int): The maximum tokens.
"""
def __init__( # noqa: D107
self,
model: str,
api_key: str,
is_full_context: bool,
max_tokens: int = 4196,
):
self.model = model
self.system_prompt = (
FULL_CONTEXT_SYSTEM_PROMPT
if is_full_context
else SINGLE_CHUNK_SYSTEM_PROMPT
)
self.max_tokens = max_tokens
self.provider = ModelProvider.from_model(model)
self.session = self.create_session(api_key)
def create_session(self, api_key: str) -> Any:
"""Create a session for the model.
Args:
api_key (str): The API key.
Returns:
Any: The session.
"""
match self.provider:
case ModelProvider.OPENAI:
return AsyncOpenAI(api_key=api_key)
case ModelProvider.ANTHROPIC:
return AsyncAnthropic(api_key=api_key)
case ModelProvider.GOOGLE:
genai.configure(api_key=api_key)
return genai.GenerativeModel(
model_name=self.model, system_instruction=self.system_prompt
)
case ModelProvider.DEEPSEEK:
return AsyncOpenAI(api_key=api_key, base_url="https://api.deepseek.com")
async def request(self, prompt: str) -> str:
"""Request the model to generate a response.
Args:
prompt (str): The prompt to generate a response for.
Returns:
str: The generated response.
"""
match self.provider:
case ModelProvider.OPENAI | ModelProvider.DEEPSEEK:
response = await self.session.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": prompt},
],
temperature=0.2,
max_tokens=self.max_tokens,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
)
return response.choices[0].message.content.strip()
case ModelProvider.ANTHROPIC:
response = await self.session.messages.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
system=[
{
"type": "text",
"text": self.system_prompt,
"cache_control": {"type": "ephemeral"},
}
],
temperature=0.2,
max_tokens=self.max_tokens,
)
return response.content[0].text.strip()
case ModelProvider.GOOGLE:
response = await self.session.generate_content_async(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=list[GoogleResponse],
),
)
return response.text.strip()
async def get_response_single_chunk(
self, file: str, title: str, description: str, chunk: str
) -> str:
"""Get the response for a single chunk.
Args:
file (str): The file name.
title (str): The pull request title.
description (str): The pull request description.
chunk (str): The diff chunk.
Returns:
str: The response.
"""
prompt = SINGLE_CHUNK_USER_PROMPT.format(file, title, description, chunk)
return await self.request(prompt)
async def get_response_full_context(
self, title: str, description: str, file_contents: list[str]
) -> str:
"""Get the response for full context.
Args:
title (str): The pull request title.
description (str): The pull request description.
file_contents (list[str]): The file contents, diffs.
Returns:
str: The response.
"""
try:
prompt = FULL_CONTEXT_USER_PROMPT.format(
title, description, "\n".join(file_contents)
)
return await self.request(prompt)
except Exception as e:
print(f"Error during full context response: {e}")
print(prompt)
return None
SINGLE_CHUNK_SYSTEM_PROMPT = (
"Your task is to review pull requests. Instructions:\n"
"- Provide the response in the following JSON format: "
"""[{{"lineNumber": int, "reviewComment": str}}] \n"""
"- lineNumber is about the line number of the code that in new file. \n"
"- lineNumber can be found at the front of each line. \n"
"- At the first number is old line number, the second number is new line number. \n"
"- If the line starts with `+`, it means the line is added. \n"
"- If the line starts with `-`, it means the line is deleted. \n"
"- Evaluate whether the code changes and additions are appropriate "
"and if the new code structure is suitable. \n"
"- Do not give positive comments or compliments. \n"
"- Provide comments and suggestions ONLY if there is something to improve"
"otherwise return an empty array. \n"
"- Write the comment in GitHub Markdown format. \n"
"- Use the given description only for the overall context "
"and only comment the code. \n"
"- Do not suggest type hint or naming convention. \n"
"- IMPORTANT: NEVER suggest adding comments to the code. \n"
)
SINGLE_CHUNK_USER_PROMPT = (
"Review the following code diff in the file "
"{} and take the pull request title and description into account "
"when writing the response. \n"
"Pull request title: {} \n"
"Pull request description: \n"
"--- \n"
"{} \n"
"--- \n"
"Git diff to review: \n"
"```diff \n"
"{} \n"
"```"
)
FULL_CONTEXT_SYSTEM_PROMPT = (
"You are an experienced software engineer specializing in reviewing pull "
"requests. Your task is to provide an overall code review summary for a PR. "
"Focus on assessing the following aspects:\n"
"1. **Code Structure & Architecture:** "
"Evaluate whether the code is well-organized, modular, "
"and adheres to clean code principles. Suggest improvements if needed.\n"
"2. **Refactoring Opportunities:** "
"Identify areas where the code can be optimized or simplified without changing "
"its behavior.\n"
"3. **Potential Future Problems:** "
"Highlight possible scalability, maintainability, or dependency issues that might "
"arise in the future based on the current implementation.\n"
"Be constructive and clear in your feedback. Avoid commenting on trivial issues "
"or syntax errors—focus on high-level feedback.\n"
"Precise instructions:\n"
"- Do not give positive comments or compliments.\n"
"- Provide comments and suggestions ONLY if there is something to improve, "
"otherwise return an empty string.\n"
"- Write the comment in GitHub Markdown format.\n"
"- Do not start with 'markdown' or '```markdown'.\n"
"- IMPORTANT: Give example code block or pseudo code if you can.\n"
)
FULL_CONTEXT_USER_PROMPT = (
"Review the following code and take the pull request title "
"and description into account when writing the response. \n"
"Pull request title: {} \n"
"Pull request description: \n"
"--- \n"
"{} \n"
"--- \n"
"Code to review: \n"
"{}"
)

View File

@@ -1,40 +1,18 @@
on: on:
push:
branches: [main, release]
pull_request: pull_request:
branches: [dev]
types: [unlabeled, opened, synchronize, reopened] types: [unlabeled, opened, synchronize, reopened]
merge_group: merge_group:
workflow_dispatch:
name: CI name: Dev-CI
# Cancel previous workflows if they are the same workflow on same ref (branch/tags)
# with the same event (push/pull_request) even they are in progress.
# This setting will help reduce the number of duplicated workflows.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env: env:
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
# Skip additional tests on Windows. They are checked on Linux and MacOS.
WINDOWS_SKIPS: >-
test_glob
test_importlib
test_io
test_os
test_pathlib
test_posixpath
test_shutil
test_venv
# configparser: https://github.com/RustPython/RustPython/issues/4995#issuecomment-1582397417
# socketserver: seems related to configparser crash.
MACOS_SKIPS: >-
test_configparser
test_socketserver
# PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently # PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently
# only run on Linux to speed up the CI. # only run on Linux to speed up the CI.
PLATFORM_INDEPENDENT_TESTS: >- PLATFORM_INDEPENDENT_TESTS: >-
test_argparse test__colorize
test_array test_array
test_asyncgen test_asyncgen
test_binop test_binop
@@ -59,7 +37,6 @@ env:
test_dis test_dis
test_enumerate test_enumerate
test_exception_variations test_exception_variations
test_exceptions
test_float test_float
test_format test_format
test_fractions test_fractions
@@ -100,12 +77,11 @@ env:
test_tuple test_tuple
test_types test_types
test_unary test_unary
test_unicode
test_unpack test_unpack
test_weakref test_weakref
test_yield_from test_yield_from
# Python version targeted by the CI. # Python version targeted by the CI.
PYTHON_VERSION: "3.11.4" PYTHON_VERSION: "3.13.1"
jobs: jobs:
rust_tests: rust_tests:
@@ -113,26 +89,12 @@ jobs:
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
name: Run rust tests name: Run rust tests
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
fail-fast: false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy components: clippy
- name: Set up the Windows environment
shell: bash
run: |
choco install llvm openssl --no-progress
echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV
if: runner.os == 'Windows'
- name: Set up the Mac environment
run: brew install autoconf automake libtool
if: runner.os == 'macOS'
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: run clippy - name: run clippy
@@ -140,38 +102,21 @@ jobs:
- name: run rust tests - name: run rust tests
run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }} run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }}
if: runner.os != 'macOS'
# temp skip ssl linking for Mac to avoid CI failure
- name: run rust tests (MacOS no ssl)
run: cargo test --workspace --exclude rustpython_wasm --verbose --no-default-features --features threading,stdlib,zlib,importlib,encodings,jit
if: runner.os == 'macOS'
- name: check compilation without threading - name: check compilation without threading
run: cargo check ${{ env.CARGO_ARGS }} run: cargo check ${{ env.CARGO_ARGS }}
- name: prepare AppleSilicon build - name: Test example projects
uses: dtolnay/rust-toolchain@stable run:
with: cargo run --manifest-path example_projects/barebone/Cargo.toml
target: aarch64-apple-darwin cargo run --manifest-path example_projects/frozen_stdlib/Cargo.toml
if: runner.os == 'macOS'
- name: Check compilation for Apple Silicon
run: cargo check --target aarch64-apple-darwin
if: runner.os == 'macOS'
- name: prepare iOS build
uses: dtolnay/rust-toolchain@stable
with:
target: aarch64-apple-ios
if: runner.os == 'macOS'
- name: Check compilation for iOS
run: cargo check --target aarch64-apple-ios
if: runner.os == 'macOS'
exotic_targets: exotic_targets:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Ensure compilation on various targets name: Ensure compilation on various targets
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
target: i686-unknown-linux-gnu target: i686-unknown-linux-gnu
@@ -211,13 +156,6 @@ jobs:
- name: Check compilation for freebsd - name: Check compilation for freebsd
run: cargo check --target x86_64-unknown-freebsd run: cargo check --target x86_64-unknown-freebsd
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-unknown-unknown
- name: Check compilation for wasm32
run: cargo check --target wasm32-unknown-unknown --no-default-features
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
target: x86_64-unknown-freebsd target: x86_64-unknown-freebsd
@@ -231,68 +169,44 @@ jobs:
uses: coolreader18/redoxer-action@v1 uses: coolreader18/redoxer-action@v1
with: with:
command: check command: check
args: --ignore-rust-version
snippets_cpython: snippets_cpython:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
name: Run snippets and cpython tests name: Run snippets and cpython tests
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
fail-fast: false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v4 - uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Set up the Windows environment
shell: bash
run: |
choco install llvm openssl --no-progress
echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV
if: runner.os == 'Windows'
- name: Set up the Mac environment
run: brew install autoconf automake libtool openssl@3
if: runner.os == 'macOS'
- uses: Swatinem/rust-cache@v2
- name: build rustpython - name: build rustpython
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }} run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }},jit
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: run snippets - name: run snippets
run: python -m pip install -r requirements.txt && pytest -v run: python -m pip install -r requirements.txt && pytest -v
working-directory: ./extra_tests working-directory: ./extra_tests
- if: runner.os == 'Linux' - name: run cpython platform-independent tests
name: run cpython platform-independent tests
run: run:
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v ${{ env.PLATFORM_INDEPENDENT_TESTS }} target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v ${{ env.PLATFORM_INDEPENDENT_TESTS }}
- if: runner.os == 'Linux' - name: run cpython platform-dependent tests (Linux)
name: run cpython platform-dependent tests (Linux)
run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
- if: runner.os == 'macOS' - name: check that --install-pip succeeds
name: run cpython platform-dependent tests (MacOS)
run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.MACOS_SKIPS }}
- if: runner.os == 'Windows'
name: run cpython platform-dependent tests (windows partial - fixme)
run:
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.WINDOWS_SKIPS }}
- if: runner.os != 'Windows'
name: check that --install-pip succeeds
run: | run: |
mkdir site-packages mkdir site-packages
target/release/rustpython --install-pip ensurepip --user target/release/rustpython --install-pip ensurepip --user
- if: runner.os != 'Windows' target/release/rustpython -m pip install six
name: Check that ensurepip succeeds. - name: Check that ensurepip succeeds.
run: | run: |
target/release/rustpython -m ensurepip target/release/rustpython -m ensurepip
target/release/rustpython -c "import pip" target/release/rustpython -c "import pip"
- if: runner.os != 'Windows' - name: Check if pip inside venv is functional
name: Check if pip inside venv is functional
run: | run: |
target/release/rustpython -m venv testvenv target/release/rustpython -m venv testvenv
testvenv/bin/rustpython -m pip install wheel testvenv/bin/rustpython -m pip install wheel
@@ -303,19 +217,19 @@ jobs:
name: Check Rust code with rustfmt and clippy name: Check Rust code with rustfmt and clippy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
components: rustfmt, clippy components: rustfmt, clippy
- name: run rustfmt - name: run rustfmt
run: cargo fmt --all -- --check run: cargo fmt --check
- name: run clippy on wasm - name: run clippy on wasm
run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: install ruff - name: install ruff
run: python -m pip install ruff run: python -m pip install ruff==0.0.291 # astral-sh/ruff#7778
- name: run python lint - name: run python lint
run: ruff extra_tests wasm examples --exclude='./.*',./Lib,./vm/Lib,./benches/ --select=E9,F63,F7,F82 --show-source run: ruff extra_tests wasm examples --exclude='./.*',./Lib,./vm/Lib,./benches/ --select=E9,F63,F7,F82 --show-source
- name: install prettier - name: install prettier
@@ -329,7 +243,7 @@ jobs:
name: Run tests under miri name: Run tests under miri
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: nightly toolchain: nightly
@@ -341,70 +255,3 @@ jobs:
# a memory leak, at least until we have proper cyclic gc # a memory leak, at least until we have proper cyclic gc
run: MIRIFLAGS='-Zmiri-ignore-leaks' cargo +nightly miri test -p rustpython-vm -- miri_test run: MIRIFLAGS='-Zmiri-ignore-leaks' cargo +nightly miri test -p rustpython-vm -- miri_test
wasm:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Check the WASM package and demo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: install geckodriver
run: |
wget https://github.com/mozilla/geckodriver/releases/download/v0.30.0/geckodriver-v0.30.0-linux64.tar.gz
mkdir geckodriver
tar -xzf geckodriver-v0.30.0-linux64.tar.gz -C geckodriver
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: python -m pip install -r requirements.txt
working-directory: ./wasm/tests
- uses: actions/setup-node@v3
- name: run test
run: |
export PATH=$PATH:`pwd`/../../geckodriver
npm install
npm run test
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/demo
- name: build notebook demo
if: github.ref == 'refs/heads/release'
run: |
npm install
npm run dist
mv dist ../demo/dist/notebook
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/notebook
- name: Deploy demo to Github Pages
if: success() && github.ref == 'refs/heads/release'
uses: peaceiris/actions-gh-pages@v2
env:
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
PUBLISH_DIR: ./wasm/demo/dist
EXTERNAL_REPOSITORY: RustPython/demo
PUBLISH_BRANCH: master
wasm-wasi:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Run snippets and cpython tests on wasm-wasi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-wasi
- uses: Swatinem/rust-cache@v2
- name: Setup Wasmer
uses: wasmerio/setup-wasmer@v2
- name: Install clang
run: sudo apt-get update && sudo apt-get install clang -y
- name: build rustpython
run: cargo build --release --target wasm32-wasi --features freeze-stdlib,stdlib --verbose
- name: run snippets
run: wasmer run --dir . target/wasm32-wasi/release/rustpython.wasm -- extra_tests/snippets/stdlib_random.py

36
.github/workflows/code-review.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Code Review
on:
pull_request:
branches: [dev]
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install aiohttp requests py-gitea openai anthropic google-generativeai
- name: Run Code Review
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
FULL_CONTEXT_MODEL: o3-mini
FULL_CONTEXT_API_KEY: ${{ secrets.OPENAI_API_KEY }}
SINGLE_CHUNK_MODEL: gemini-2.0-flash-exp
SINGLE_CHUNK_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
EXCLUDE: "*.yml,*.yaml"
run: python .gitea/scripts/code_review.py

View File

@@ -2,12 +2,15 @@ on:
schedule: schedule:
- cron: '0 0 * * 6' - cron: '0 0 * * 6'
workflow_dispatch: workflow_dispatch:
push:
paths:
- .github/workflows/cron-ci.yaml
name: Periodic checks/tasks name: Periodic checks/tasks
env: env:
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl,jit
PYTHON_VERSION: "3.11.4" PYTHON_VERSION: "3.13.1"
jobs: jobs:
# codecov collects code coverage data from the rust tests, python snippets and python test suite. # codecov collects code coverage data from the rust tests, python snippets and python test suite.
@@ -16,15 +19,15 @@ jobs:
name: Collect code coverage data name: Collect code coverage data
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- run: sudo apt-get update && sudo apt-get -y install lcov - run: sudo apt-get update && sudo apt-get -y install lcov
- name: Run cargo-llvm-cov with Rust tests. - name: Run cargo-llvm-cov with Rust tests.
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --verbose --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --verbose --no-default-features --features stdlib,importlib,encodings,ssl,jit
- name: Run cargo-llvm-cov with Python snippets. - name: Run cargo-llvm-cov with Python snippets.
run: python scripts/cargo-llvm-cov.py run: python scripts/cargo-llvm-cov.py
continue-on-error: true continue-on-error: true
@@ -34,7 +37,7 @@ jobs:
- name: Prepare code coverage data - name: Prepare code coverage data
run: cargo llvm-cov report --lcov --output-path='codecov.lcov' run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
- name: Upload to Codecov - name: Upload to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v5
with: with:
file: ./codecov.lcov file: ./codecov.lcov
@@ -42,7 +45,7 @@ jobs:
name: Collect regression test data name: Collect regression test data
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: build rustpython - name: build rustpython
run: cargo build --release --verbose run: cargo build --release --verbose
@@ -71,9 +74,9 @@ jobs:
name: Collect what is left data name: Collect what is left data
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: build rustpython - name: build rustpython
@@ -97,6 +100,9 @@ jobs:
cd website cd website
[ -f ./_data/whats_left.temp ] && cp ./_data/whats_left.temp ./_data/whats_left_lastrun.temp [ -f ./_data/whats_left.temp ] && cp ./_data/whats_left.temp ./_data/whats_left_lastrun.temp
cp ../whats_left.temp ./_data/whats_left.temp cp ../whats_left.temp ./_data/whats_left.temp
rm ./_data/whats_left/modules.csv
echo -e "module" > ./_data/whats_left/modules.csv
cat ./_data/whats_left.temp | grep "(entire module)" | cut -d ' ' -f 1 | sort >> ./_data/whats_left/modules.csv
git add -A git add -A
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update what is left results" --author="$GITHUB_ACTOR"; then if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update what is left results" --author="$GITHUB_ACTOR"; then
git push git push
@@ -106,9 +112,9 @@ jobs:
name: Collect benchmark data name: Collect benchmark data
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: 3.9 python-version: 3.9
- run: cargo install cargo-criterion - run: cargo install cargo-criterion

171
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,171 @@
name: Release
on:
schedule:
# 9 AM UTC on every Monday
- cron: "0 9 * * Mon"
workflow_dispatch:
inputs:
pre-release:
type: boolean
description: Mark "Pre-Release"
required: false
default: true
permissions:
contents: write
env:
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,sqlite,ssl
jobs:
build:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
# - runner: ubuntu-latest
# target: i686-unknown-linux-gnu
# - runner: ubuntu-latest
# target: aarch64-unknown-linux-gnu
# - runner: ubuntu-latest
# target: armv7-unknown-linux-gnueabi
# - runner: ubuntu-latest
# target: s390x-unknown-linux-gnu
# - runner: ubuntu-latest
# target: powerpc64le-unknown-linux-gnu
- runner: macos-latest
target: aarch64-apple-darwin
# - runner: macos-latest
# target: x86_64-apple-darwin
- runner: windows-latest
target: x86_64-pc-windows-msvc
# - runner: windows-latest
# target: i686-pc-windows-msvc
# - runner: windows-latest
# target: aarch64-pc-windows-msvc
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Set up Environment
shell: bash
run: rustup target add ${{ matrix.platform.target }}
- name: Set up Windows Environment
shell: bash
run: |
cargo install --target-dir=target -v cargo-vcpkg
cargo vcpkg -v build
if: runner.os == 'Windows'
- name: Set up MacOS Environment
run: brew install autoconf automake libtool
if: runner.os == 'macOS'
- name: Build RustPython
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }}
if: runner.os == 'macOS'
- name: Build RustPython
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }},jit
if: runner.os != 'macOS'
- name: Rename Binary
run: cp target/${{ matrix.platform.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
if: runner.os != 'Windows'
- name: Rename Binary
run: cp target/${{ matrix.platform.target }}/release/rustpython.exe target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}.exe
if: runner.os == 'Windows'
- name: Upload Binary Artifacts
uses: actions/upload-artifact@v4
with:
name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}*
build-wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
- name: Build RustPython
run: cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
- name: Rename Binary
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
- name: Upload Binary Artifacts
uses: actions/upload-artifact@v4
with:
name: rustpython-release-wasm32-wasip1
path: target/rustpython-release-wasm32-wasip1.wasm
- name: install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- uses: actions/setup-node@v4
- uses: mwilliamson/setup-wabt-action@v3
with: { wabt-version: "1.0.30" }
- name: build demo
run: |
npm install
npm run dist
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/demo
- name: build notebook demo
run: |
npm install
npm run dist
mv dist ../demo/dist/notebook
env:
NODE_OPTIONS: "--openssl-legacy-provider"
working-directory: ./wasm/notebook
- name: Deploy demo to Github Pages
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
publish_dir: ./wasm/demo/dist
external_repository: RustPython/demo
publish_branch: master
release:
runs-on: ubuntu-latest
needs: [build, build-wasm]
steps:
- name: Download Binary Artifacts
uses: actions/download-artifact@v4
with:
path: bin
pattern: rustpython-release-*
merge-multiple: true
- name: List Binaries
run: |
ls -lah bin/
file bin/*
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: ${{ github.run_number }}
run: |
if [[ "${{ github.event.inputs.pre-release }}" == "false" ]]; then
RELEASE_TYPE_NAME=Release
PRERELEASE_ARG=
else
RELEASE_TYPE_NAME=Pre-Release
PRERELEASE_ARG=--prerelease
fi
today=$(date '+%Y-%m-%d')
gh release create "$today-$tag-$run" \
--repo="$GITHUB_REPOSITORY" \
--title="RustPython $RELEASE_TYPE_NAME $today-$tag #$run" \
--target="$tag" \
--generate-notes \
$PRERELEASE_ARG \
bin/rustpython-release-*

2
.gitignore vendored
View File

@@ -9,6 +9,8 @@ __pycache__
.vscode .vscode
wasm-pack.log wasm-pack.log
.idea/ .idea/
.envrc
.python-version
flame-graph.html flame-graph.html
flame.txt flame.txt

1998
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +1,35 @@
[package] [package]
name = "rustpython" name = "rustpython"
version = "0.3.0"
authors = ["RustPython Team"]
edition = "2021"
rust-version = "1.67.1"
description = "A python interpreter written in rust." description = "A python interpreter written in rust."
repository = "https://github.com/RustPython/RustPython"
license = "MIT"
include = ["LICENSE", "Cargo.toml", "src/**/*.rs"] include = ["LICENSE", "Cargo.toml", "src/**/*.rs"]
version.workspace = true
[workspace] authors.workspace = true
resolver = "2" edition.workspace = true
members = [ rust-version.workspace = true
"compiler", "compiler/core", "compiler/codegen", repository.workspace = true
".", "common", "derive", "jit", "vm", "pylib", "stdlib", "wasm/lib", "derive-impl", license.workspace = true
]
[workspace.dependencies]
rustpython-compiler-core = { path = "compiler/core", version = "0.3.0" }
rustpython-compiler = { path = "compiler", version = "0.3.0" }
rustpython-codegen = { path = "compiler/codegen", version = "0.3.0" }
rustpython-common = { path = "common", version = "0.3.0" }
rustpython-derive = { path = "derive", version = "0.3.0" }
rustpython-derive-impl = { path = "derive-impl", version = "0.3.0" }
rustpython-jit = { path = "jit", version = "0.3.0" }
rustpython-vm = { path = "vm", version = "0.3.0" }
rustpython-pylib = { path = "pylib", version = "0.3.0" }
rustpython-stdlib = { path = "stdlib", version = "0.3.0" }
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
# rustpython-literal = { path = "../RustPython-parser/literal" }
# rustpython-parser-core = { path = "../RustPython-parser/core" }
# rustpython-parser = { path = "../RustPython-parser/parser" }
# rustpython-ast = { path = "../RustPython-parser/ast" }
# rustpython-format = { path = "../RustPython-parser/format" }
ahash = "0.7.6"
anyhow = "1.0.45"
ascii = "1.0"
atty = "0.2.14"
bitflags = "2.2.1"
bstr = "0.2.17"
cfg-if = "1.0"
chrono = "0.4.19"
crossbeam-utils = "0.8.16"
flame = "0.2.2"
glob = "0.3"
hex = "0.4.3"
indexmap = "1.8.1"
insta = "1.14.0"
itertools = "0.10.3"
libc = "0.2.133"
log = "0.4.16"
nix = "0.26"
malachite-bigint = { version = "0.1.0" }
malachite-q = "0.3.2"
malachite-base = "0.3.2"
num-complex = "0.4.0"
num-integer = "0.1.44"
num-traits = "0.2"
num_enum = "0.5.7"
once_cell = "1.13"
parking_lot = "0.12"
paste = "1.0.7"
rand = "0.8.5"
rustyline = "11"
serde = "1.0"
schannel = "0.1.19"
static_assertions = "1.1"
syn = "1.0.91"
thiserror = "1.0"
thread_local = "1.1.4"
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
widestring = "0.5.1"
[features] [features]
default = ["threading", "stdlib", "zlib", "importlib"] default = ["threading", "stdlib", "importlib"]
importlib = ["rustpython-vm/importlib"] importlib = ["rustpython-vm/importlib"]
encodings = ["rustpython-vm/encodings"] encodings = ["rustpython-vm/encodings"]
stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"] stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"]
flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"] flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"]
freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"] freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
jit = ["rustpython-vm/jit"] jit = ["rustpython-vm/jit"]
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"] threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
zlib = ["stdlib", "rustpython-stdlib/zlib"]
bz2 = ["stdlib", "rustpython-stdlib/bz2"] bz2 = ["stdlib", "rustpython-stdlib/bz2"]
sqlite = ["rustpython-stdlib/sqlite"]
ssl = ["rustpython-stdlib/ssl"] ssl = ["rustpython-stdlib/ssl"]
ssl-vendor = ["rustpython-stdlib/ssl-vendor"] ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"]
[dependencies] [dependencies]
rustpython-compiler = { workspace = true } rustpython-compiler = { workspace = true }
rustpython-pylib = { workspace = true, optional = true } rustpython-pylib = { workspace = true, optional = true }
rustpython-stdlib = { workspace = true, optional = true } rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
rustpython-vm = { workspace = true, default-features = false, features = ["compiler"] } rustpython-vm = { workspace = true, features = ["compiler"] }
rustpython-parser = { workspace = true } rustpython-parser = { workspace = true }
atty = { workspace = true }
cfg-if = { workspace = true } cfg-if = { workspace = true }
log = { workspace = true } log = { workspace = true }
flame = { workspace = true, optional = true } flame = { workspace = true, optional = true }
@@ -117,9 +46,8 @@ libc = { workspace = true }
rustyline = { workspace = true } rustyline = { workspace = true }
[dev-dependencies] [dev-dependencies]
cpython = "0.7.0" criterion = { workspace = true }
criterion = "0.3.5" pyo3 = { version = "0.22", features = ["auto-initialize"] }
python3-sys = "0.7.1"
[[bench]] [[bench]]
name = "execution" name = "execution"
@@ -151,5 +79,121 @@ lto = "thin"
[patch.crates-io] [patch.crates-io]
# REDOX START, Uncomment when you want to compile/check with redoxer # REDOX START, Uncomment when you want to compile/check with redoxer
# nix = { git = "https://github.com/coolreader18/nix", branch = "0.26.2-redox" }
# REDOX END # REDOX END
# Used only on Windows to build the vcpkg dependencies
[package.metadata.vcpkg]
git = "https://github.com/microsoft/vcpkg"
# The revision of the vcpkg repository to use
# https://github.com/microsoft/vcpkg/tags
rev = "2024.02.14"
[package.metadata.vcpkg.target]
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = ["openssl" ] }
[workspace]
resolver = "2"
members = [
"compiler", "compiler/core", "compiler/codegen",
".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "derive-impl",
"wasm/lib",
]
[workspace.package]
version = "0.4.0"
authors = ["RustPython Team"]
edition = "2024"
rust-version = "1.85.0"
repository = "https://github.com/RustPython/RustPython"
license = "MIT"
[workspace.dependencies]
rustpython-compiler-core = { path = "compiler/core", version = "0.4.0" }
rustpython-compiler = { path = "compiler", version = "0.4.0" }
rustpython-codegen = { path = "compiler/codegen", version = "0.4.0" }
rustpython-common = { path = "common", version = "0.4.0" }
rustpython-derive = { path = "derive", version = "0.4.0" }
rustpython-derive-impl = { path = "derive-impl", version = "0.4.0" }
rustpython-jit = { path = "jit", version = "0.4.0" }
rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" }
rustpython-pylib = { path = "pylib", version = "0.4.0" }
rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.0" }
rustpython-sre_engine = { path = "vm/sre_engine", version = "0.4.0" }
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
# rustpython-literal = { version = "0.4.0" }
# rustpython-parser-core = { version = "0.4.0" }
# rustpython-parser = { version = "0.4.0" }
# rustpython-ast = { version = "0.4.0" }
# rustpython-format= { version = "0.4.0" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
# rustpython-literal = { path = "../RustPython-parser/literal" }
# rustpython-parser-core = { path = "../RustPython-parser/core" }
# rustpython-parser = { path = "../RustPython-parser/parser" }
# rustpython-ast = { path = "../RustPython-parser/ast" }
# rustpython-format = { path = "../RustPython-parser/format" }
ahash = "0.8.11"
ascii = "1.1"
bitflags = "2.4.2"
bstr = "1"
cfg-if = "1.0"
chrono = "0.4.39"
criterion = { version = "0.3.5", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
flame = "0.2.2"
getrandom = "0.3"
glob = "0.3"
hex = "0.4.3"
indexmap = { version = "2.2.6", features = ["std"] }
insta = "1.38.0"
itertools = "0.14.0"
is-macro = "0.3.7"
junction = "1.2.0"
libc = "0.2.169"
log = "0.4.25"
nix = { version = "0.29", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
malachite-bigint = "0.2.3"
malachite-q = "0.4.22"
malachite-base = "0.4.22"
memchr = "2.7.4"
num-complex = "0.4.6"
num-integer = "0.1.46"
num-traits = "0.2"
num_enum = { version = "0.7", default-features = false }
once_cell = "1.20.3"
parking_lot = "0.12.3"
paste = "1.0.15"
rand = "0.9"
rustix = { version = "0.38", features = ["event"] }
rustyline = "15.0.0"
serde = { version = "1.0.133", default-features = false }
schannel = "0.1.27"
static_assertions = "1.1"
strum = "0.27"
strum_macros = "0.27"
syn = "2"
thiserror = "2.0"
thread_local = "1.1.8"
unicode_names2 = "1.3.0"
widestring = "1.1.0"
windows-sys = "0.59.0"
wasm-bindgen = "0.2.100"
# Lints
[workspace.lints.rust]
unsafe_code = "allow"
unsafe_op_in_unsafe_fn = "deny"
elided_lifetimes_in_paths = "warn"
[workspace.lints.clippy]
perf = "warn"
style = "warn"
complexity = "warn"
suspicious = "warn"
correctness = "warn"

View File

@@ -25,7 +25,7 @@ RustPython requires the following:
stable version: `rustup update stable` stable version: `rustup update stable`
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to - If you do not have Rust installed, use [rustup](https://rustup.rs/) to
do so. do so.
- CPython version 3.11 or higher - CPython version 3.13 or higher
- CPython can be installed by your operating system's package manager, - CPython can be installed by your operating system's package manager,
from the [Python website](https://www.python.org/downloads/), or from the [Python website](https://www.python.org/downloads/), or
using a third-party distribution, such as using a third-party distribution, such as

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020 RustPython Team Copyright (c) 2025 RustPython Team
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

6
Lib/__future__.py vendored
View File

@@ -33,7 +33,7 @@ in releases at or after that, modules no longer need
to use the feature in question, but may continue to use such imports. to use the feature in question, but may continue to use such imports.
MandatoryRelease may also be None, meaning that a planned feature got MandatoryRelease may also be None, meaning that a planned feature got
dropped. dropped or that the release version is undetermined.
Instances of class _Feature have two corresponding methods, Instances of class _Feature have two corresponding methods,
.getOptionalRelease() and .getMandatoryRelease(). .getOptionalRelease() and .getMandatoryRelease().
@@ -96,7 +96,7 @@ class _Feature:
"""Return release in which this feature will become mandatory. """Return release in which this feature will become mandatory.
This is a 5-tuple, of the same form as sys.version_info, or, if This is a 5-tuple, of the same form as sys.version_info, or, if
the feature was dropped, is None. the feature was dropped, or the release date is undetermined, is None.
""" """
return self.mandatory return self.mandatory
@@ -143,5 +143,5 @@ generator_stop = _Feature((3, 5, 0, "beta", 1),
CO_FUTURE_GENERATOR_STOP) CO_FUTURE_GENERATOR_STOP)
annotations = _Feature((3, 7, 0, "beta", 1), annotations = _Feature((3, 7, 0, "beta", 1),
(3, 11, 0, "alpha", 0), None,
CO_FUTURE_ANNOTATIONS) CO_FUTURE_ANNOTATIONS)

181
Lib/_android_support.py vendored Normal file
View File

@@ -0,0 +1,181 @@
import io
import sys
from threading import RLock
from time import sleep, time
# The maximum length of a log message in bytes, including the level marker and
# tag, is defined as LOGGER_ENTRY_MAX_PAYLOAD at
# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log.h;l=71.
# Messages longer than this will be truncated by logcat. This limit has already
# been reduced at least once in the history of Android (from 4076 to 4068 between
# API level 23 and 26), so leave some headroom.
MAX_BYTES_PER_WRITE = 4000
# UTF-8 uses a maximum of 4 bytes per character, so limiting text writes to this
# size ensures that we can always avoid exceeding MAX_BYTES_PER_WRITE.
# However, if the actual number of bytes per character is smaller than that,
# then we may still join multiple consecutive text writes into binary
# writes containing a larger number of characters.
MAX_CHARS_PER_WRITE = MAX_BYTES_PER_WRITE // 4
# When embedded in an app on current versions of Android, there's no easy way to
# monitor the C-level stdout and stderr. The testbed comes with a .c file to
# redirect them to the system log using a pipe, but that wouldn't be convenient
# or appropriate for all apps. So we redirect at the Python level instead.
def init_streams(android_log_write, stdout_prio, stderr_prio):
if sys.executable:
return # Not embedded in an app.
global logcat
logcat = Logcat(android_log_write)
sys.stdout = TextLogStream(
stdout_prio, "python.stdout", sys.stdout.fileno())
sys.stderr = TextLogStream(
stderr_prio, "python.stderr", sys.stderr.fileno())
class TextLogStream(io.TextIOWrapper):
def __init__(self, prio, tag, fileno=None, **kwargs):
# The default is surrogateescape for stdout and backslashreplace for
# stderr, but in the context of an Android log, readability is more
# important than reversibility.
kwargs.setdefault("encoding", "UTF-8")
kwargs.setdefault("errors", "backslashreplace")
super().__init__(BinaryLogStream(prio, tag, fileno), **kwargs)
self._lock = RLock()
self._pending_bytes = []
self._pending_bytes_count = 0
def __repr__(self):
return f"<TextLogStream {self.buffer.tag!r}>"
def write(self, s):
if not isinstance(s, str):
raise TypeError(
f"write() argument must be str, not {type(s).__name__}")
# In case `s` is a str subclass that writes itself to stdout or stderr
# when we call its methods, convert it to an actual str.
s = str.__str__(s)
# We want to emit one log message per line wherever possible, so split
# the string into lines first. Note that "".splitlines() == [], so
# nothing will be logged for an empty string.
with self._lock:
for line in s.splitlines(keepends=True):
while line:
chunk = line[:MAX_CHARS_PER_WRITE]
line = line[MAX_CHARS_PER_WRITE:]
self._write_chunk(chunk)
return len(s)
# The size and behavior of TextIOWrapper's buffer is not part of its public
# API, so we handle buffering ourselves to avoid truncation.
def _write_chunk(self, s):
b = s.encode(self.encoding, self.errors)
if self._pending_bytes_count + len(b) > MAX_BYTES_PER_WRITE:
self.flush()
self._pending_bytes.append(b)
self._pending_bytes_count += len(b)
if (
self.write_through
or b.endswith(b"\n")
or self._pending_bytes_count > MAX_BYTES_PER_WRITE
):
self.flush()
def flush(self):
with self._lock:
self.buffer.write(b"".join(self._pending_bytes))
self._pending_bytes.clear()
self._pending_bytes_count = 0
# Since this is a line-based logging system, line buffering cannot be turned
# off, i.e. a newline always causes a flush.
@property
def line_buffering(self):
return True
class BinaryLogStream(io.RawIOBase):
def __init__(self, prio, tag, fileno=None):
self.prio = prio
self.tag = tag
self._fileno = fileno
def __repr__(self):
return f"<BinaryLogStream {self.tag!r}>"
def writable(self):
return True
def write(self, b):
if type(b) is not bytes:
try:
b = bytes(memoryview(b))
except TypeError:
raise TypeError(
f"write() argument must be bytes-like, not {type(b).__name__}"
) from None
# Writing an empty string to the stream should have no effect.
if b:
logcat.write(self.prio, self.tag, b)
return len(b)
# This is needed by the test suite --timeout option, which uses faulthandler.
def fileno(self):
if self._fileno is None:
raise io.UnsupportedOperation("fileno")
return self._fileno
# When a large volume of data is written to logcat at once, e.g. when a test
# module fails in --verbose3 mode, there's a risk of overflowing logcat's own
# buffer and losing messages. We avoid this by imposing a rate limit using the
# token bucket algorithm, based on a conservative estimate of how fast `adb
# logcat` can consume data.
MAX_BYTES_PER_SECOND = 1024 * 1024
# The logcat buffer size of a device can be determined by running `logcat -g`.
# We set the token bucket size to half of the buffer size of our current minimum
# API level, because other things on the system will be producing messages as
# well.
BUCKET_SIZE = 128 * 1024
# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log_read.h;l=39
PER_MESSAGE_OVERHEAD = 28
class Logcat:
def __init__(self, android_log_write):
self.android_log_write = android_log_write
self._lock = RLock()
self._bucket_level = 0
self._prev_write_time = time()
def write(self, prio, tag, message):
# Encode null bytes using "modified UTF-8" to avoid them truncating the
# message.
message = message.replace(b"\x00", b"\xc0\x80")
with self._lock:
now = time()
self._bucket_level += (
(now - self._prev_write_time) * MAX_BYTES_PER_SECOND)
# If the bucket level is still below zero, the clock must have gone
# backwards, so reset it to zero and continue.
self._bucket_level = max(0, min(self._bucket_level, BUCKET_SIZE))
self._prev_write_time = now
self._bucket_level -= PER_MESSAGE_OVERHEAD + len(tag) + len(message)
if self._bucket_level < 0:
sleep(-self._bucket_level / MAX_BYTES_PER_SECOND)
self.android_log_write(prio, tag, message)

66
Lib/_apple_support.py vendored Normal file
View File

@@ -0,0 +1,66 @@
import io
import sys
def init_streams(log_write, stdout_level, stderr_level):
# Redirect stdout and stderr to the Apple system log. This method is
# invoked by init_apple_streams() (initconfig.c) if config->use_system_logger
# is enabled.
sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors)
sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors)
class SystemLog(io.TextIOWrapper):
def __init__(self, log_write, level, **kwargs):
kwargs.setdefault("encoding", "UTF-8")
kwargs.setdefault("line_buffering", True)
super().__init__(LogStream(log_write, level), **kwargs)
def __repr__(self):
return f"<SystemLog (level {self.buffer.level})>"
def write(self, s):
if not isinstance(s, str):
raise TypeError(
f"write() argument must be str, not {type(s).__name__}")
# In case `s` is a str subclass that writes itself to stdout or stderr
# when we call its methods, convert it to an actual str.
s = str.__str__(s)
# We want to emit one log message per line, so split
# the string before sending it to the superclass.
for line in s.splitlines(keepends=True):
super().write(line)
return len(s)
class LogStream(io.RawIOBase):
def __init__(self, log_write, level):
self.log_write = log_write
self.level = level
def __repr__(self):
return f"<LogStream (level {self.level!r})>"
def writable(self):
return True
def write(self, b):
if type(b) is not bytes:
try:
b = bytes(memoryview(b))
except TypeError:
raise TypeError(
f"write() argument must be bytes-like, not {type(b).__name__}"
) from None
# Writing an empty string to the stream should have no effect.
if b:
# Encode null bytes using "modified UTF-8" to avoid truncating the
# message. This should not affect the return value, as the caller
# may be expecting it to match the length of the input.
self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80"))
return len(b)

View File

@@ -6,6 +6,32 @@
Unit tests are in test_collections. Unit tests are in test_collections.
""" """
############ Maintenance notes #########################################
#
# ABCs are different from other standard library modules in that they
# specify compliance tests. In general, once an ABC has been published,
# new methods (either abstract or concrete) cannot be added.
#
# Though classes that inherit from an ABC would automatically receive a
# new mixin method, registered classes would become non-compliant and
# violate the contract promised by ``isinstance(someobj, SomeABC)``.
#
# Though irritating, the correct procedure for adding new abstract or
# mixin methods is to create a new ABC as a subclass of the previous
# ABC. For example, union(), intersection(), and difference() cannot
# be added to Set but could go into a new ABC that extends Set.
#
# Because they are so hard to change, new ABCs should have their APIs
# carefully thought through prior to publication.
#
# Since ABCMeta only checks for the presence of methods, it is possible
# to alter the signature of a method by adding optional arguments
# or changing parameters names. This is still a bit dubious but at
# least it won't cause isinstance() to return an incorrect result.
#
#
#######################################################################
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import sys import sys
@@ -23,7 +49,7 @@ __all__ = ["Awaitable", "Coroutine",
"Mapping", "MutableMapping", "Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView", "MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence", "Sequence", "MutableSequence",
"ByteString", "ByteString", "Buffer",
] ]
# This module has been renamed from collections.abc to _collections_abc to # This module has been renamed from collections.abc to _collections_abc to
@@ -413,6 +439,21 @@ class Collection(Sized, Iterable, Container):
return NotImplemented return NotImplemented
class Buffer(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __buffer__(self, flags: int, /) -> memoryview:
raise NotImplementedError
@classmethod
def __subclasshook__(cls, C):
if cls is Buffer:
return _check_methods(C, "__buffer__")
return NotImplemented
class _CallableGenericAlias(GenericAlias): class _CallableGenericAlias(GenericAlias):
""" Represent `Callable[argtypes, resulttype]`. """ Represent `Callable[argtypes, resulttype]`.
@@ -455,15 +496,8 @@ class _CallableGenericAlias(GenericAlias):
# rather than the default types.GenericAlias object. Most of the # rather than the default types.GenericAlias object. Most of the
# code is copied from typing's _GenericAlias and the builtin # code is copied from typing's _GenericAlias and the builtin
# types.GenericAlias. # types.GenericAlias.
if not isinstance(item, tuple): if not isinstance(item, tuple):
item = (item,) item = (item,)
# A special case in PEP 612 where if X = Callable[P, int],
# then X[int, str] == X[[int, str]].
if (len(self.__parameters__) == 1
and _is_param_expr(self.__parameters__[0])
and item and not _is_param_expr(item[0])):
item = (item,)
new_args = super().__getitem__(item).__args__ new_args = super().__getitem__(item).__args__
@@ -491,9 +525,8 @@ def _type_repr(obj):
Copied from :mod:`typing` since collections.abc Copied from :mod:`typing` since collections.abc
shouldn't depend on that module. shouldn't depend on that module.
(Keep this roughly in sync with the typing version.)
""" """
if isinstance(obj, GenericAlias):
return repr(obj)
if isinstance(obj, type): if isinstance(obj, type):
if obj.__module__ == 'builtins': if obj.__module__ == 'builtins':
return obj.__qualname__ return obj.__qualname__
@@ -1038,8 +1071,27 @@ Sequence.register(str)
Sequence.register(range) Sequence.register(range)
Sequence.register(memoryview) Sequence.register(memoryview)
class _DeprecateByteStringMeta(ABCMeta):
def __new__(cls, name, bases, namespace, **kwargs):
if name != "ByteString":
import warnings
class ByteString(Sequence): warnings._deprecated(
"collections.abc.ByteString",
remove=(3, 14),
)
return super().__new__(cls, name, bases, namespace, **kwargs)
def __instancecheck__(cls, instance):
import warnings
warnings._deprecated(
"collections.abc.ByteString",
remove=(3, 14),
)
return super().__instancecheck__(instance)
class ByteString(Sequence, metaclass=_DeprecateByteStringMeta):
"""This unifies bytes and bytearray. """This unifies bytes and bytearray.
XXX Should add all their methods. XXX Should add all their methods.

67
Lib/_colorize.py vendored Normal file
View File

@@ -0,0 +1,67 @@
import io
import os
import sys
COLORIZE = True
class ANSIColors:
BOLD_GREEN = "\x1b[1;32m"
BOLD_MAGENTA = "\x1b[1;35m"
BOLD_RED = "\x1b[1;31m"
GREEN = "\x1b[32m"
GREY = "\x1b[90m"
MAGENTA = "\x1b[35m"
RED = "\x1b[31m"
RESET = "\x1b[0m"
YELLOW = "\x1b[33m"
NoColors = ANSIColors()
for attr in dir(NoColors):
if not attr.startswith("__"):
setattr(NoColors, attr, "")
def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
if colorize or can_colorize(file=file):
return ANSIColors()
else:
return NoColors
def can_colorize(*, file=None) -> bool:
if file is None:
file = sys.stdout
if not sys.flags.ignore_environment:
if os.environ.get("PYTHON_COLORS") == "0":
return False
if os.environ.get("PYTHON_COLORS") == "1":
return True
if os.environ.get("NO_COLOR"):
return False
if not COLORIZE:
return False
if os.environ.get("FORCE_COLOR"):
return True
if os.environ.get("TERM") == "dumb":
return False
if not hasattr(file, "fileno"):
return False
if sys.platform == "win32":
try:
import nt
if not nt._supports_virtual_terminal():
return False
except (ImportError, AttributeError):
return False
try:
return os.isatty(file.fileno())
except io.UnsupportedOperation:
return file.isatty()

22
Lib/_dummy_os.py vendored
View File

@@ -5,22 +5,30 @@ A shim of the os module containing only simple path-related utilities
try: try:
from os import * from os import *
except ImportError: except ImportError:
import abc import abc, sys
def __getattr__(name): def __getattr__(name):
raise OSError("no os specific module found") if name in {"_path_normpath", "__path__"}:
raise AttributeError(name)
if name.isupper():
return 0
def dummy(*args, **kwargs):
import io
return io.UnsupportedOperation(f"{name}: no os specific module found")
dummy.__name__ = f"dummy_{name}"
return dummy
def _shim(): sys.modules['os'] = sys.modules['posix'] = sys.modules[__name__]
import _dummy_os, sys
sys.modules['os'] = _dummy_os
sys.modules['os.path'] = _dummy_os.path
import posixpath as path import posixpath as path
import sys
sys.modules['os.path'] = path sys.modules['os.path'] = path
del sys del sys
sep = path.sep sep = path.sep
supports_dir_fd = set()
supports_effective_ids = set()
supports_fd = set()
supports_follow_symlinks = set()
def fspath(path): def fspath(path):

71
Lib/_ios_support.py vendored Normal file
View File

@@ -0,0 +1,71 @@
import sys
try:
from ctypes import cdll, c_void_p, c_char_p, util
except ImportError:
# ctypes is an optional module. If it's not present, we're limited in what
# we can tell about the system, but we don't want to prevent the module
# from working.
print("ctypes isn't available; iOS system calls will not be available", file=sys.stderr)
objc = None
else:
# ctypes is available. Load the ObjC library, and wrap the objc_getClass,
# sel_registerName methods
lib = util.find_library("objc")
if lib is None:
# Failed to load the objc library
raise ImportError("ObjC runtime library couldn't be loaded")
objc = cdll.LoadLibrary(lib)
objc.objc_getClass.restype = c_void_p
objc.objc_getClass.argtypes = [c_char_p]
objc.sel_registerName.restype = c_void_p
objc.sel_registerName.argtypes = [c_char_p]
def get_platform_ios():
# Determine if this is a simulator using the multiarch value
is_simulator = sys.implementation._multiarch.endswith("simulator")
# We can't use ctypes; abort
if not objc:
return None
# Most of the methods return ObjC objects
objc.objc_msgSend.restype = c_void_p
# All the methods used have no arguments.
objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
# Equivalent of:
# device = [UIDevice currentDevice]
UIDevice = objc.objc_getClass(b"UIDevice")
SEL_currentDevice = objc.sel_registerName(b"currentDevice")
device = objc.objc_msgSend(UIDevice, SEL_currentDevice)
# Equivalent of:
# device_systemVersion = [device systemVersion]
SEL_systemVersion = objc.sel_registerName(b"systemVersion")
device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion)
# Equivalent of:
# device_systemName = [device systemName]
SEL_systemName = objc.sel_registerName(b"systemName")
device_systemName = objc.objc_msgSend(device, SEL_systemName)
# Equivalent of:
# device_model = [device model]
SEL_model = objc.sel_registerName(b"model")
device_model = objc.objc_msgSend(device, SEL_model)
# UTF8String returns a const char*;
SEL_UTF8String = objc.sel_registerName(b"UTF8String")
objc.objc_msgSend.restype = c_char_p
# Equivalent of:
# system = [device_systemName UTF8String]
# release = [device_systemVersion UTF8String]
# model = [device_model UTF8String]
system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode()
release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode()
model = objc.objc_msgSend(device_model, SEL_UTF8String).decode()
return system, release, model, is_simulator

5
Lib/_osx_support.py vendored
View File

@@ -507,6 +507,11 @@ def get_platform_osx(_config_vars, osname, release, machine):
# MACOSX_DEPLOYMENT_TARGET. # MACOSX_DEPLOYMENT_TARGET.
macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '') macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
if macver and '.' not in macver:
# Ensure that the version includes at least a major
# and minor version, even if MACOSX_DEPLOYMENT_TARGET
# is set to a single-label version like "14".
macver += '.0'
macrelease = _get_system_version() or macver macrelease = _get_system_version() or macver
macver = macver or macrelease macver = macver or macrelease

2643
Lib/_pydatetime.py vendored Normal file

File diff suppressed because it is too large Load Diff

27
Lib/_pydecimal.py vendored
View File

@@ -734,18 +734,23 @@ class Decimal(object):
""" """
if isinstance(f, int): # handle integer inputs if isinstance(f, int): # handle integer inputs
return cls(f) sign = 0 if f >= 0 else 1
if not isinstance(f, float): k = 0
raise TypeError("argument must be int or float.") coeff = str(abs(f))
if _math.isinf(f) or _math.isnan(f): elif isinstance(f, float):
return cls(repr(f)) if _math.isinf(f) or _math.isnan(f):
if _math.copysign(1.0, f) == 1.0: return cls(repr(f))
sign = 0 if _math.copysign(1.0, f) == 1.0:
sign = 0
else:
sign = 1
n, d = abs(f).as_integer_ratio()
k = d.bit_length() - 1
coeff = str(n*5**k)
else: else:
sign = 1 raise TypeError("argument must be int or float.")
n, d = abs(f).as_integer_ratio()
k = d.bit_length() - 1 result = _dec_from_triple(sign, coeff, -k)
result = _dec_from_triple(sign, str(n*5**k), -k)
if cls is Decimal: if cls is Decimal:
return result return result
else: else:

90
Lib/_pyio.py vendored
View File

@@ -44,8 +44,9 @@ def text_encoding(encoding, stacklevel=2):
""" """
A helper function to choose the text encoding. A helper function to choose the text encoding.
When encoding is not None, just return it. When encoding is not None, this function returns it.
Otherwise, return the default text encoding (i.e. "locale"). Otherwise, this function returns the default text encoding
(i.e. "locale" or "utf-8" depends on UTF-8 mode).
This function emits an EncodingWarning if *encoding* is None and This function emits an EncodingWarning if *encoding* is None and
sys.flags.warn_default_encoding is true. sys.flags.warn_default_encoding is true.
@@ -55,7 +56,10 @@ def text_encoding(encoding, stacklevel=2):
However, please consider using encoding="utf-8" for new APIs. However, please consider using encoding="utf-8" for new APIs.
""" """
if encoding is None: if encoding is None:
encoding = "locale" if sys.flags.utf8_mode:
encoding = "utf-8"
else:
encoding = "locale"
if sys.flags.warn_default_encoding: if sys.flags.warn_default_encoding:
import warnings import warnings
warnings.warn("'encoding' argument not specified.", warnings.warn("'encoding' argument not specified.",
@@ -101,7 +105,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
'b' binary mode 'b' binary mode
't' text mode (default) 't' text mode (default)
'+' open a disk file for updating (reading and writing) '+' open a disk file for updating (reading and writing)
'U' universal newline mode (deprecated)
========= =============================================================== ========= ===============================================================
The default mode is 'rt' (open for reading text). For binary random The default mode is 'rt' (open for reading text). For binary random
@@ -117,10 +120,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
returned as strings, the bytes having been first decoded using a returned as strings, the bytes having been first decoded using a
platform-dependent encoding or using the specified encoding if given. platform-dependent encoding or using the specified encoding if given.
'U' mode is deprecated and will raise an exception in future versions
of Python. It has no effect in Python 3. Use newline to control
universal newlines mode.
buffering is an optional integer used to set the buffering policy. buffering is an optional integer used to set the buffering policy.
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
line buffering (only usable in text mode), and an integer > 1 to indicate line buffering (only usable in text mode), and an integer > 1 to indicate
@@ -206,7 +205,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
if errors is not None and not isinstance(errors, str): if errors is not None and not isinstance(errors, str):
raise TypeError("invalid errors: %r" % errors) raise TypeError("invalid errors: %r" % errors)
modes = set(mode) modes = set(mode)
if modes - set("axrwb+tU") or len(mode) > len(modes): if modes - set("axrwb+t") or len(mode) > len(modes):
raise ValueError("invalid mode: %r" % mode) raise ValueError("invalid mode: %r" % mode)
creating = "x" in modes creating = "x" in modes
reading = "r" in modes reading = "r" in modes
@@ -215,13 +214,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
updating = "+" in modes updating = "+" in modes
text = "t" in modes text = "t" in modes
binary = "b" in modes binary = "b" in modes
if "U" in modes:
if creating or writing or appending or updating:
raise ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'")
import warnings
warnings.warn("'U' mode is deprecated",
DeprecationWarning, 2)
reading = True
if text and binary: if text and binary:
raise ValueError("can't have text and binary mode at once") raise ValueError("can't have text and binary mode at once")
if creating + reading + writing + appending > 1: if creating + reading + writing + appending > 1:
@@ -311,22 +303,6 @@ except AttributeError:
open_code = _open_code_with_warning open_code = _open_code_with_warning
def __getattr__(name):
if name == "OpenWrapper":
# bpo-43680: Until Python 3.9, _pyio.open was not a static method and
# builtins.open was set to OpenWrapper to not become a bound method
# when set to a class variable. _io.open is a built-in function whereas
# _pyio.open is a Python function. In Python 3.10, _pyio.open() is now
# a static method, and builtins.open() is now io.open().
import warnings
warnings.warn('OpenWrapper is deprecated, use open instead',
DeprecationWarning, stacklevel=2)
global OpenWrapper
OpenWrapper = open
return OpenWrapper
raise AttributeError(name)
# In normal operation, both `UnsupportedOperation`s should be bound to the # In normal operation, both `UnsupportedOperation`s should be bound to the
# same object. # same object.
try: try:
@@ -338,8 +314,7 @@ except AttributeError:
class IOBase(metaclass=abc.ABCMeta): class IOBase(metaclass=abc.ABCMeta):
"""The abstract base class for all I/O classes, acting on streams of """The abstract base class for all I/O classes.
bytes. There is no public constructor.
This class provides dummy implementations for many methods that This class provides dummy implementations for many methods that
derived classes can override selectively; the default implementations derived classes can override selectively; the default implementations
@@ -1154,6 +1129,7 @@ class BufferedReader(_BufferedIOMixin):
do at most one raw read to satisfy it. We never return more do at most one raw read to satisfy it. We never return more
than self.buffer_size. than self.buffer_size.
""" """
self._checkClosed("peek of closed file")
with self._read_lock: with self._read_lock:
return self._peek_unlocked(size) return self._peek_unlocked(size)
@@ -1172,6 +1148,7 @@ class BufferedReader(_BufferedIOMixin):
"""Reads up to size bytes, with at most one read() system call.""" """Reads up to size bytes, with at most one read() system call."""
# Returns up to size bytes. If at least one byte is buffered, we # Returns up to size bytes. If at least one byte is buffered, we
# only return buffered bytes. Otherwise, we do one raw read. # only return buffered bytes. Otherwise, we do one raw read.
self._checkClosed("read of closed file")
if size < 0: if size < 0:
size = self.buffer_size size = self.buffer_size
if size == 0: if size == 0:
@@ -1189,6 +1166,8 @@ class BufferedReader(_BufferedIOMixin):
def _readinto(self, buf, read1): def _readinto(self, buf, read1):
"""Read data into *buf* with at most one system call.""" """Read data into *buf* with at most one system call."""
self._checkClosed("readinto of closed file")
# Need to create a memoryview object of type 'b', otherwise # Need to create a memoryview object of type 'b', otherwise
# we may not be able to assign bytes to it, and slicing it # we may not be able to assign bytes to it, and slicing it
# would create a new object. # would create a new object.
@@ -1233,11 +1212,13 @@ class BufferedReader(_BufferedIOMixin):
return written return written
def tell(self): def tell(self):
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos # GH-95782: Keep return value non-negative
return max(_BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos, 0)
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
if whence not in valid_seek_flags: if whence not in valid_seek_flags:
raise ValueError("invalid whence value") raise ValueError("invalid whence value")
self._checkClosed("seek of closed file")
with self._read_lock: with self._read_lock:
if whence == 1: if whence == 1:
pos -= len(self._read_buf) - self._read_pos pos -= len(self._read_buf) - self._read_pos
@@ -1845,7 +1826,7 @@ class TextIOBase(IOBase):
"""Base class for text I/O. """Base class for text I/O.
This class provides a character and line based interface to stream This class provides a character and line based interface to stream
I/O. There is no public constructor. I/O.
""" """
def read(self, size=-1): def read(self, size=-1):
@@ -1997,7 +1978,7 @@ class TextIOWrapper(TextIOBase):
r"""Character and line based layer over a BufferedIOBase object, buffer. r"""Character and line based layer over a BufferedIOBase object, buffer.
encoding gives the name of the encoding that the stream will be encoding gives the name of the encoding that the stream will be
decoded or encoded with. It defaults to locale.getpreferredencoding(False). decoded or encoded with. It defaults to locale.getencoding().
errors determines the strictness of encoding and decoding (see the errors determines the strictness of encoding and decoding (see the
codecs.register) and defaults to "strict". codecs.register) and defaults to "strict".
@@ -2031,19 +2012,7 @@ class TextIOWrapper(TextIOBase):
encoding = text_encoding(encoding) encoding = text_encoding(encoding)
if encoding == "locale": if encoding == "locale":
try: encoding = self._get_locale_encoding()
encoding = os.device_encoding(buffer.fileno()) or "locale"
except (AttributeError, UnsupportedOperation):
pass
if encoding == "locale":
try:
import locale
except ImportError:
# Importing locale may fail if Python is being built
encoding = "utf-8"
else:
encoding = locale.getpreferredencoding(False)
if not isinstance(encoding, str): if not isinstance(encoding, str):
raise ValueError("invalid encoding: %r" % encoding) raise ValueError("invalid encoding: %r" % encoding)
@@ -2176,6 +2145,8 @@ class TextIOWrapper(TextIOBase):
else: else:
if not isinstance(encoding, str): if not isinstance(encoding, str):
raise TypeError("invalid encoding: %r" % encoding) raise TypeError("invalid encoding: %r" % encoding)
if encoding == "locale":
encoding = self._get_locale_encoding()
if newline is Ellipsis: if newline is Ellipsis:
newline = self._readnl newline = self._readnl
@@ -2243,8 +2214,9 @@ class TextIOWrapper(TextIOBase):
self.buffer.write(b) self.buffer.write(b)
if self._line_buffering and (haslf or "\r" in s): if self._line_buffering and (haslf or "\r" in s):
self.flush() self.flush()
self._set_decoded_chars('') if self._snapshot is not None:
self._snapshot = None self._set_decoded_chars('')
self._snapshot = None
if self._decoder: if self._decoder:
self._decoder.reset() self._decoder.reset()
return length return length
@@ -2280,6 +2252,15 @@ class TextIOWrapper(TextIOBase):
self._decoded_chars_used += len(chars) self._decoded_chars_used += len(chars)
return chars return chars
def _get_locale_encoding(self):
try:
import locale
except ImportError:
# Importing locale may fail if Python is being built
return "utf-8"
else:
return locale.getencoding()
def _rewind_decoded_chars(self, n): def _rewind_decoded_chars(self, n):
"""Rewind the _decoded_chars buffer.""" """Rewind the _decoded_chars buffer."""
if self._decoded_chars_used < n: if self._decoded_chars_used < n:
@@ -2549,8 +2530,9 @@ class TextIOWrapper(TextIOBase):
# Read everything. # Read everything.
result = (self._get_decoded_chars() + result = (self._get_decoded_chars() +
decoder.decode(self.buffer.read(), final=True)) decoder.decode(self.buffer.read(), final=True))
self._set_decoded_chars('') if self._snapshot is not None:
self._snapshot = None self._set_decoded_chars('')
self._snapshot = None
return result return result
else: else:
# Keep reading chunks until we have size characters to return. # Keep reading chunks until we have size characters to return.

View File

@@ -10,7 +10,6 @@ The objects used by the site module to add custom builtins.
import sys import sys
class Quitter(object): class Quitter(object):
def __init__(self, name, eof): def __init__(self, name, eof):
self.name = name self.name = name
@@ -48,7 +47,7 @@ class _Printer(object):
data = None data = None
for filename in self.__filenames: for filename in self.__filenames:
try: try:
with open(filename, "r") as fp: with open(filename, encoding='utf-8') as fp:
data = fp.read() data = fp.read()
break break
except OSError: except OSError:

565
Lib/_strptime.py vendored Normal file
View File

@@ -0,0 +1,565 @@
"""Strptime-related classes and functions.
CLASSES:
LocaleTime -- Discovers and stores locale-specific time information
TimeRE -- Creates regexes for pattern matching a string of text containing
time information
FUNCTIONS:
_getlang -- Figure out what language is being used for the locale
strptime -- Calculates the time struct represented by the passed-in string
"""
import time
import locale
import calendar
from re import compile as re_compile
from re import IGNORECASE
from re import escape as re_escape
from datetime import (date as datetime_date,
timedelta as datetime_timedelta,
timezone as datetime_timezone)
from _thread import allocate_lock as _thread_allocate_lock
__all__ = []
def _getlang():
# Figure out what the current language is set to.
return locale.getlocale(locale.LC_TIME)
class LocaleTime(object):
"""Stores and handles locale-specific information related to time.
ATTRIBUTES:
f_weekday -- full weekday names (7-item list)
a_weekday -- abbreviated weekday names (7-item list)
f_month -- full month names (13-item list; dummy value in [0], which
is added by code)
a_month -- abbreviated month names (13-item list, dummy value in
[0], which is added by code)
am_pm -- AM/PM representation (2-item list)
LC_date_time -- format string for date/time representation (string)
LC_date -- format string for date representation (string)
LC_time -- format string for time representation (string)
timezone -- daylight- and non-daylight-savings timezone representation
(2-item list of sets)
lang -- Language used by instance (2-item tuple)
"""
def __init__(self):
"""Set all attributes.
Order of methods called matters for dependency reasons.
The locale language is set at the offset and then checked again before
exiting. This is to make sure that the attributes were not set with a
mix of information from more than one locale. This would most likely
happen when using threads where one thread calls a locale-dependent
function while another thread changes the locale while the function in
the other thread is still running. Proper coding would call for
locks to prevent changing the locale while locale-dependent code is
running. The check here is done in case someone does not think about
doing this.
Only other possible issue is if someone changed the timezone and did
not call tz.tzset . That is an issue for the programmer, though,
since changing the timezone is worthless without that call.
"""
self.lang = _getlang()
self.__calc_weekday()
self.__calc_month()
self.__calc_am_pm()
self.__calc_timezone()
self.__calc_date_time()
if _getlang() != self.lang:
raise ValueError("locale changed during initialization")
if time.tzname != self.tzname or time.daylight != self.daylight:
raise ValueError("timezone changed during initialization")
def __calc_weekday(self):
# Set self.a_weekday and self.f_weekday using the calendar
# module.
a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]
f_weekday = [calendar.day_name[i].lower() for i in range(7)]
self.a_weekday = a_weekday
self.f_weekday = f_weekday
def __calc_month(self):
# Set self.f_month and self.a_month using the calendar module.
a_month = [calendar.month_abbr[i].lower() for i in range(13)]
f_month = [calendar.month_name[i].lower() for i in range(13)]
self.a_month = a_month
self.f_month = f_month
def __calc_am_pm(self):
# Set self.am_pm by using time.strftime().
# The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
# magical; just happened to have used it everywhere else where a
# static date was needed.
am_pm = []
for hour in (1, 22):
time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
am_pm.append(time.strftime("%p", time_tuple).lower())
self.am_pm = am_pm
def __calc_date_time(self):
# Set self.date_time, self.date, & self.time by using
# time.strftime().
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
# overloaded numbers is minimized. The order in which searches for
# values within the format string is very important; it eliminates
# possible ambiguity for what something represents.
time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
date_time = [None, None, None]
date_time[0] = time.strftime("%c", time_tuple).lower()
date_time[1] = time.strftime("%x", time_tuple).lower()
date_time[2] = time.strftime("%X", time_tuple).lower()
replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
('44', '%M'), ('55', '%S'), ('76', '%j'),
('17', '%d'), ('03', '%m'), ('3', '%m'),
# '3' needed for when no leading zero.
('2', '%w'), ('10', '%I')]
replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
for tz in tz_values])
for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
current_format = date_time[offset]
for old, new in replacement_pairs:
# Must deal with possible lack of locale info
# manifesting itself as the empty string (e.g., Swedish's
# lack of AM/PM info) or a platform returning a tuple of empty
# strings (e.g., MacOS 9 having timezone as ('','')).
if old:
current_format = current_format.replace(old, new)
# If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
# 2005-01-03 occurs before the first Monday of the year. Otherwise
# %U is used.
time_tuple = time.struct_time((1999,1,3,1,1,1,6,3,0))
if '00' in time.strftime(directive, time_tuple):
U_W = '%W'
else:
U_W = '%U'
date_time[offset] = current_format.replace('11', U_W)
self.LC_date_time = date_time[0]
self.LC_date = date_time[1]
self.LC_time = date_time[2]
def __calc_timezone(self):
# Set self.timezone by using time.tzname.
# Do not worry about possibility of time.tzname[0] == time.tzname[1]
# and time.daylight; handle that in strptime.
try:
time.tzset()
except AttributeError:
pass
self.tzname = time.tzname
self.daylight = time.daylight
no_saving = frozenset({"utc", "gmt", self.tzname[0].lower()})
if self.daylight:
has_saving = frozenset({self.tzname[1].lower()})
else:
has_saving = frozenset()
self.timezone = (no_saving, has_saving)
class TimeRE(dict):
"""Handle conversion from format directives to regexes."""
def __init__(self, locale_time=None):
"""Create keys/values.
Order of execution is important for dependency reasons.
"""
if locale_time:
self.locale_time = locale_time
else:
self.locale_time = LocaleTime()
base = super()
base.__init__({
# The " [1-9]" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
'G': r"(?P<G>\d\d\d\d)",
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
'M': r"(?P<M>[0-5]\d|\d)",
'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
'w': r"(?P<w>[0-6])",
'u': r"(?P<u>[1-7])",
'V': r"(?P<V>5[0-3]|0[1-9]|[1-4]\d|\d)",
# W is set below by using 'U'
'y': r"(?P<y>\d\d)",
#XXX: Does 'Y' need to worry about having less or more than
# 4 digits?
'Y': r"(?P<Y>\d\d\d\d)",
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone
for tz in tz_names),
'Z'),
'%': '%'})
base.__setitem__('W', base.__getitem__('U').replace('U', 'W'))
base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
base.__setitem__('x', self.pattern(self.locale_time.LC_date))
base.__setitem__('X', self.pattern(self.locale_time.LC_time))
def __seqToRE(self, to_convert, directive):
"""Convert a list to a regex string for matching a directive.
Want possible matching values to be from longest to shortest. This
prevents the possibility of a match occurring for a value that also
a substring of a larger value that should have matched (e.g., 'abc'
matching when 'abcdef' should have been the match).
"""
to_convert = sorted(to_convert, key=len, reverse=True)
for value in to_convert:
if value != '':
break
else:
return ''
regex = '|'.join(re_escape(stuff) for stuff in to_convert)
regex = '(?P<%s>%s' % (directive, regex)
return '%s)' % regex
def pattern(self, format):
"""Return regex pattern for the format string.
Need to make sure that any characters that might be interpreted as
regex syntax are escaped.
"""
processed_format = ''
# The sub() call escapes all characters that might be misconstrued
# as regex syntax. Cannot use re.escape since we have to deal with
# format directives (%m, etc.).
regex_chars = re_compile(r"([\\.^$*+?\(\){}\[\]|])")
format = regex_chars.sub(r"\\\1", format)
whitespace_replacement = re_compile(r'\s+')
format = whitespace_replacement.sub(r'\\s+', format)
while '%' in format:
directive_index = format.index('%')+1
processed_format = "%s%s%s" % (processed_format,
format[:directive_index-1],
self[format[directive_index]])
format = format[directive_index+1:]
return "%s%s" % (processed_format, format)
def compile(self, format):
"""Return a compiled re object for the format string."""
return re_compile(self.pattern(format), IGNORECASE)
_cache_lock = _thread_allocate_lock()
# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
# first!
_TimeRE_cache = TimeRE()
_CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache
_regex_cache = {}
def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
"""Calculate the Julian day based on the year, week of the year, and day of
the week, with week_start_day representing whether the week of the year
assumes the week starts on Sunday or Monday (6 or 0)."""
first_weekday = datetime_date(year, 1, 1).weekday()
# If we are dealing with the %U directive (week starts on Sunday), it's
# easier to just shift the view to Sunday being the first day of the
# week.
if not week_starts_Mon:
first_weekday = (first_weekday + 1) % 7
day_of_week = (day_of_week + 1) % 7
# Need to watch out for a week 0 (when the first day of the year is not
# the same as that specified by %U or %W).
week_0_length = (7 - first_weekday) % 7
if week_of_year == 0:
return 1 + day_of_week - first_weekday
else:
days_to_week = week_0_length + (7 * (week_of_year - 1))
return 1 + days_to_week + day_of_week
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a 2-tuple consisting of a time struct and an int containing
the number of microseconds based on the input string and the
format string."""
for index, arg in enumerate([data_string, format]):
if not isinstance(arg, str):
msg = "strptime() argument {} must be str, not {}"
raise TypeError(msg.format(index, type(arg)))
global _TimeRE_cache, _regex_cache
with _cache_lock:
locale_time = _TimeRE_cache.locale_time
if (_getlang() != locale_time.lang or
time.tzname != locale_time.tzname or
time.daylight != locale_time.daylight):
_TimeRE_cache = TimeRE()
_regex_cache.clear()
locale_time = _TimeRE_cache.locale_time
if len(_regex_cache) > _CACHE_MAX_SIZE:
_regex_cache.clear()
format_regex = _regex_cache.get(format)
if not format_regex:
try:
format_regex = _TimeRE_cache.compile(format)
# KeyError raised when a bad format is found; can be specified as
# \\, in which case it was a stray % but with a space after it
except KeyError as err:
bad_directive = err.args[0]
if bad_directive == "\\":
bad_directive = "%"
del err
raise ValueError("'%s' is a bad directive in format '%s'" %
(bad_directive, format)) from None
# IndexError only occurs when the format string is "%"
except IndexError:
raise ValueError("stray %% in format '%s'" % format) from None
_regex_cache[format] = format_regex
found = format_regex.match(data_string)
if not found:
raise ValueError("time data %r does not match format %r" %
(data_string, format))
if len(data_string) != found.end():
raise ValueError("unconverted data remains: %s" %
data_string[found.end():])
iso_year = year = None
month = day = 1
hour = minute = second = fraction = 0
tz = -1
gmtoff = None
gmtoff_fraction = 0
iso_week = week_of_year = None
week_of_year_start = None
# weekday and julian defaulted to None so as to signal need to calculate
# values
weekday = julian = None
found_dict = found.groupdict()
for group_key in found_dict.keys():
# Directives not explicitly handled below:
# c, x, X
# handled by making out of other directives
# U, W
# worthless without day of the week
if group_key == 'y':
year = int(found_dict['y'])
# Open Group specification for strptime() states that a %y
#value in the range of [00, 68] is in the century 2000, while
#[69,99] is in the century 1900
if year <= 68:
year += 2000
else:
year += 1900
elif group_key == 'Y':
year = int(found_dict['Y'])
elif group_key == 'G':
iso_year = int(found_dict['G'])
elif group_key == 'm':
month = int(found_dict['m'])
elif group_key == 'B':
month = locale_time.f_month.index(found_dict['B'].lower())
elif group_key == 'b':
month = locale_time.a_month.index(found_dict['b'].lower())
elif group_key == 'd':
day = int(found_dict['d'])
elif group_key == 'H':
hour = int(found_dict['H'])
elif group_key == 'I':
hour = int(found_dict['I'])
ampm = found_dict.get('p', '').lower()
# If there was no AM/PM indicator, we'll treat this like AM
if ampm in ('', locale_time.am_pm[0]):
# We're in AM so the hour is correct unless we're
# looking at 12 midnight.
# 12 midnight == 12 AM == hour 0
if hour == 12:
hour = 0
elif ampm == locale_time.am_pm[1]:
# We're in PM so we need to add 12 to the hour unless
# we're looking at 12 noon.
# 12 noon == 12 PM == hour 12
if hour != 12:
hour += 12
elif group_key == 'M':
minute = int(found_dict['M'])
elif group_key == 'S':
second = int(found_dict['S'])
elif group_key == 'f':
s = found_dict['f']
# Pad to always return microseconds.
s += "0" * (6 - len(s))
fraction = int(s)
elif group_key == 'A':
weekday = locale_time.f_weekday.index(found_dict['A'].lower())
elif group_key == 'a':
weekday = locale_time.a_weekday.index(found_dict['a'].lower())
elif group_key == 'w':
weekday = int(found_dict['w'])
if weekday == 0:
weekday = 6
else:
weekday -= 1
elif group_key == 'u':
weekday = int(found_dict['u'])
weekday -= 1
elif group_key == 'j':
julian = int(found_dict['j'])
elif group_key in ('U', 'W'):
week_of_year = int(found_dict[group_key])
if group_key == 'U':
# U starts week on Sunday.
week_of_year_start = 6
else:
# W starts week on Monday.
week_of_year_start = 0
elif group_key == 'V':
iso_week = int(found_dict['V'])
elif group_key == 'z':
z = found_dict['z']
if z == 'Z':
gmtoff = 0
else:
if z[3] == ':':
z = z[:3] + z[4:]
if len(z) > 5:
if z[5] != ':':
msg = f"Inconsistent use of : in {found_dict['z']}"
raise ValueError(msg)
z = z[:5] + z[6:]
hours = int(z[1:3])
minutes = int(z[3:5])
seconds = int(z[5:7] or 0)
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
gmtoff_remainder = z[8:]
# Pad to always return microseconds.
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
if z.startswith("-"):
gmtoff = -gmtoff
gmtoff_fraction = -gmtoff_fraction
elif group_key == 'Z':
# Since -1 is default value only need to worry about setting tz if
# it can be something other than -1.
found_zone = found_dict['Z'].lower()
for value, tz_values in enumerate(locale_time.timezone):
if found_zone in tz_values:
# Deal with bad locale setup where timezone names are the
# same and yet time.daylight is true; too ambiguous to
# be able to tell what timezone has daylight savings
if (time.tzname[0] == time.tzname[1] and
time.daylight and found_zone not in ("utc", "gmt")):
break
else:
tz = value
break
# Deal with the cases where ambiguities arise
# don't assume default values for ISO week/year
if iso_year is not None:
if julian is not None:
raise ValueError("Day of the year directive '%j' is not "
"compatible with ISO year directive '%G'. "
"Use '%Y' instead.")
elif iso_week is None or weekday is None:
raise ValueError("ISO year directive '%G' must be used with "
"the ISO week directive '%V' and a weekday "
"directive ('%A', '%a', '%w', or '%u').")
elif iso_week is not None:
if year is None or weekday is None:
raise ValueError("ISO week directive '%V' must be used with "
"the ISO year directive '%G' and a weekday "
"directive ('%A', '%a', '%w', or '%u').")
else:
raise ValueError("ISO week directive '%V' is incompatible with "
"the year directive '%Y'. Use the ISO year '%G' "
"instead.")
leap_year_fix = False
if year is None:
if month == 2 and day == 29:
year = 1904 # 1904 is first leap year of 20th century
leap_year_fix = True
else:
year = 1900
# If we know the week of the year and what day of that week, we can figure
# out the Julian day of the year.
if julian is None and weekday is not None:
if week_of_year is not None:
week_starts_Mon = True if week_of_year_start == 0 else False
julian = _calc_julian_from_U_or_W(year, week_of_year, weekday,
week_starts_Mon)
elif iso_year is not None and iso_week is not None:
datetime_result = datetime_date.fromisocalendar(iso_year, iso_week, weekday + 1)
year = datetime_result.year
month = datetime_result.month
day = datetime_result.day
if julian is not None and julian <= 0:
year -= 1
yday = 366 if calendar.isleap(year) else 365
julian += yday
if julian is None:
# Cannot pre-calculate datetime_date() since can change in Julian
# calculation and thus could have different value for the day of
# the week calculation.
# Need to add 1 to result since first day of the year is 1, not 0.
julian = datetime_date(year, month, day).toordinal() - \
datetime_date(year, 1, 1).toordinal() + 1
else: # Assume that if they bothered to include Julian day (or if it was
# calculated above with year/week/weekday) it will be accurate.
datetime_result = datetime_date.fromordinal(
(julian - 1) +
datetime_date(year, 1, 1).toordinal())
year = datetime_result.year
month = datetime_result.month
day = datetime_result.day
if weekday is None:
weekday = datetime_date(year, month, day).weekday()
# Add timezone info
tzname = found_dict.get("Z")
if leap_year_fix:
# the caller didn't supply a year but asked for Feb 29th. We couldn't
# use the default of 1900 for computations. We set it back to ensure
# that February 29th is smaller than March 1st.
year = 1900
return (year, month, day,
hour, minute, second,
weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a time struct based on the input string and the
format string."""
tt = _strptime(data_string, format)[0]
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a class cls instance based on the input string and the
format string."""
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
tzname, gmtoff = tt[-2:]
args = tt[:6] + (fraction,)
if gmtoff is not None:
tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
if tzname:
tz = datetime_timezone(tzdelta, tzname)
else:
tz = datetime_timezone(tzdelta)
args += (tz,)
return cls(*args)

2
Lib/abc.py vendored
View File

@@ -18,7 +18,7 @@ def abstractmethod(funcobj):
class C(metaclass=ABCMeta): class C(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def my_abstract_method(self, ...): def my_abstract_method(self, arg1, arg2, argN):
... ...
""" """
funcobj.__isabstractmethod__ = True funcobj.__isabstractmethod__ = True

64
Lib/argparse.py vendored
View File

@@ -345,21 +345,22 @@ class HelpFormatter(object):
def get_lines(parts, indent, prefix=None): def get_lines(parts, indent, prefix=None):
lines = [] lines = []
line = [] line = []
indent_length = len(indent)
if prefix is not None: if prefix is not None:
line_len = len(prefix) - 1 line_len = len(prefix) - 1
else: else:
line_len = len(indent) - 1 line_len = indent_length - 1
for part in parts: for part in parts:
if line_len + 1 + len(part) > text_width and line: if line_len + 1 + len(part) > text_width and line:
lines.append(indent + ' '.join(line)) lines.append(indent + ' '.join(line))
line = [] line = []
line_len = len(indent) - 1 line_len = indent_length - 1
line.append(part) line.append(part)
line_len += len(part) + 1 line_len += len(part) + 1
if line: if line:
lines.append(indent + ' '.join(line)) lines.append(indent + ' '.join(line))
if prefix is not None: if prefix is not None:
lines[0] = lines[0][len(indent):] lines[0] = lines[0][indent_length:]
return lines return lines
# if prog is short, follow it with optionals or positionals # if prog is short, follow it with optionals or positionals
@@ -403,10 +404,18 @@ class HelpFormatter(object):
except ValueError: except ValueError:
continue continue
else: else:
end = start + len(group._group_actions) group_action_count = len(group._group_actions)
end = start + group_action_count
if actions[start:end] == group._group_actions: if actions[start:end] == group._group_actions:
suppressed_actions_count = 0
for action in group._group_actions: for action in group._group_actions:
group_actions.add(action) group_actions.add(action)
if action.help is SUPPRESS:
suppressed_actions_count += 1
exposed_actions_count = group_action_count - suppressed_actions_count
if not group.required: if not group.required:
if start in inserts: if start in inserts:
inserts[start] += ' [' inserts[start] += ' ['
@@ -416,7 +425,7 @@ class HelpFormatter(object):
inserts[end] += ']' inserts[end] += ']'
else: else:
inserts[end] = ']' inserts[end] = ']'
else: elif exposed_actions_count > 1:
if start in inserts: if start in inserts:
inserts[start] += ' (' inserts[start] += ' ('
else: else:
@@ -490,7 +499,6 @@ class HelpFormatter(object):
text = _re.sub(r'(%s) ' % open, r'\1', text) text = _re.sub(r'(%s) ' % open, r'\1', text)
text = _re.sub(r' (%s)' % close, r'\1', text) text = _re.sub(r' (%s)' % close, r'\1', text)
text = _re.sub(r'%s *%s' % (open, close), r'', text) text = _re.sub(r'%s *%s' % (open, close), r'', text)
text = _re.sub(r'\(([^|]*)\)', r'\1', text)
text = text.strip() text = text.strip()
# return the text # return the text
@@ -875,16 +883,19 @@ class Action(_AttributeHolder):
raise NotImplementedError(_('.__call__() not defined')) raise NotImplementedError(_('.__call__() not defined'))
# FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
_deprecated_default = object()
class BooleanOptionalAction(Action): class BooleanOptionalAction(Action):
def __init__(self, def __init__(self,
option_strings, option_strings,
dest, dest,
default=None, default=None,
type=None, type=_deprecated_default,
choices=None, choices=_deprecated_default,
required=False, required=False,
help=None, help=None,
metavar=None): metavar=_deprecated_default):
_option_strings = [] _option_strings = []
for option_string in option_strings: for option_string in option_strings:
@@ -894,6 +905,24 @@ class BooleanOptionalAction(Action):
option_string = '--no-' + option_string[2:] option_string = '--no-' + option_string[2:]
_option_strings.append(option_string) _option_strings.append(option_string)
# We need `_deprecated` special value to ban explicit arguments that
# match default value. Like:
# parser.add_argument('-f', action=BooleanOptionalAction, type=int)
for field_name in ('type', 'choices', 'metavar'):
if locals()[field_name] is not _deprecated_default:
warnings._deprecated(
field_name,
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
if type is _deprecated_default:
type = None
if choices is _deprecated_default:
choices = None
if metavar is _deprecated_default:
metavar = None
super().__init__( super().__init__(
option_strings=_option_strings, option_strings=_option_strings,
dest=dest, dest=dest,
@@ -2165,7 +2194,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# replace arguments referencing files with the file content # replace arguments referencing files with the file content
else: else:
try: try:
with open(arg_string[1:]) as args_file: with open(arg_string[1:],
encoding=_sys.getfilesystemencoding(),
errors=_sys.getfilesystemencodeerrors()) as args_file:
arg_strings = [] arg_strings = []
for arg_line in args_file.read().splitlines(): for arg_line in args_file.read().splitlines():
for arg in self.convert_arg_line_to_args(arg_line): for arg in self.convert_arg_line_to_args(arg_line):
@@ -2479,9 +2510,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
not action.option_strings): not action.option_strings):
if action.default is not None: if action.default is not None:
value = action.default value = action.default
self._check_value(action, value)
else: else:
# since arg_strings is always [] at this point
# there is no need to use self._check_value(action, value)
value = arg_strings value = arg_strings
self._check_value(action, value)
# single argument or optional argument produces a single value # single argument or optional argument produces a single value
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
@@ -2523,7 +2556,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# ArgumentTypeErrors indicate errors # ArgumentTypeErrors indicate errors
except ArgumentTypeError as err: except ArgumentTypeError as err:
name = getattr(action.type, '__name__', repr(action.type))
msg = str(err) msg = str(err)
raise ArgumentError(action, msg) raise ArgumentError(action, msg)
@@ -2595,9 +2627,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
def _print_message(self, message, file=None): def _print_message(self, message, file=None):
if message: if message:
if file is None: file = file or _sys.stderr
file = _sys.stderr try:
file.write(message) file.write(message)
except (AttributeError, OSError):
pass
# =============== # ===============
# Exiting methods # Exiting methods

350
Lib/ast.py vendored
View File

@@ -25,9 +25,10 @@
:license: Python License. :license: Python License.
""" """
import sys import sys
import re
from _ast import * from _ast import *
from contextlib import contextmanager, nullcontext from contextlib import contextmanager, nullcontext
from enum import IntEnum, auto from enum import IntEnum, auto, _simple_enum
def parse(source, filename='<unknown>', mode='exec', *, def parse(source, filename='<unknown>', mode='exec', *,
@@ -40,12 +41,13 @@ def parse(source, filename='<unknown>', mode='exec', *,
flags = PyCF_ONLY_AST flags = PyCF_ONLY_AST
if type_comments: if type_comments:
flags |= PyCF_TYPE_COMMENTS flags |= PyCF_TYPE_COMMENTS
if isinstance(feature_version, tuple): if feature_version is None:
major, minor = feature_version # Should be a 2-tuple.
assert major == 3
feature_version = minor
elif feature_version is None:
feature_version = -1 feature_version = -1
elif isinstance(feature_version, tuple):
major, minor = feature_version # Should be a 2-tuple.
if major != 3:
raise ValueError(f"Unsupported major version: {major}")
feature_version = minor
# Else it should be an int giving the minor version for 3.x. # Else it should be an int giving the minor version for 3.x.
return compile(source, filename, mode, flags, return compile(source, filename, mode, flags,
_feature_version=feature_version) _feature_version=feature_version)
@@ -292,9 +294,7 @@ def get_docstring(node, clean=True):
if not(node.body and isinstance(node.body[0], Expr)): if not(node.body and isinstance(node.body[0], Expr)):
return None return None
node = node.body[0].value node = node.body[0].value
if isinstance(node, Str): if isinstance(node, Constant) and isinstance(node.value, str):
text = node.s
elif isinstance(node, Constant) and isinstance(node.value, str):
text = node.value text = node.value
else: else:
return None return None
@@ -304,28 +304,17 @@ def get_docstring(node, clean=True):
return text return text
def _splitlines_no_ff(source): _line_pattern = re.compile(r"(.*?(?:\r\n|\n|\r|$))")
def _splitlines_no_ff(source, maxlines=None):
"""Split a string into lines ignoring form feed and other chars. """Split a string into lines ignoring form feed and other chars.
This mimics how the Python parser splits source code. This mimics how the Python parser splits source code.
""" """
idx = 0
lines = [] lines = []
next_line = '' for lineno, match in enumerate(_line_pattern.finditer(source), 1):
while idx < len(source): if maxlines is not None and lineno > maxlines:
c = source[idx] break
next_line += c lines.append(match[0])
idx += 1
# Keep \r\n together
if c == '\r' and idx < len(source) and source[idx] == '\n':
next_line += '\n'
idx += 1
if c in '\r\n':
lines.append(next_line)
next_line = ''
if next_line:
lines.append(next_line)
return lines return lines
@@ -359,7 +348,7 @@ def get_source_segment(source, node, *, padded=False):
except AttributeError: except AttributeError:
return None return None
lines = _splitlines_no_ff(source) lines = _splitlines_no_ff(source, maxlines=end_lineno+1)
if end_lineno == lineno: if end_lineno == lineno:
return lines[lineno].encode()[col_offset:end_col_offset].decode() return lines[lineno].encode()[col_offset:end_col_offset].decode()
@@ -508,20 +497,52 @@ class NodeTransformer(NodeVisitor):
return node return node
_DEPRECATED_VALUE_ALIAS_MESSAGE = (
"{name} is deprecated and will be removed in Python {remove}; use value instead"
)
_DEPRECATED_CLASS_MESSAGE = (
"{name} is deprecated and will be removed in Python {remove}; "
"use ast.Constant instead"
)
# If the ast module is loaded more than once, only add deprecated methods once # If the ast module is loaded more than once, only add deprecated methods once
if not hasattr(Constant, 'n'): if not hasattr(Constant, 'n'):
# The following code is for backward compatibility. # The following code is for backward compatibility.
# It will be removed in future. # It will be removed in future.
def _getter(self): def _n_getter(self):
"""Deprecated. Use value instead.""" """Deprecated. Use value instead."""
import warnings
warnings._deprecated(
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value return self.value
def _setter(self, value): def _n_setter(self, value):
import warnings
warnings._deprecated(
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
self.value = value self.value = value
Constant.n = property(_getter, _setter) def _s_getter(self):
Constant.s = property(_getter, _setter) """Deprecated. Use value instead."""
import warnings
warnings._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value
def _s_setter(self, value):
import warnings
warnings._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
self.value = value
Constant.n = property(_n_getter, _n_setter)
Constant.s = property(_s_getter, _s_setter)
class _ABC(type): class _ABC(type):
@@ -529,6 +550,13 @@ class _ABC(type):
cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead""" cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead"""
def __instancecheck__(cls, inst): def __instancecheck__(cls, inst):
if cls in _const_types:
import warnings
warnings._deprecated(
f"ast.{cls.__qualname__}",
message=_DEPRECATED_CLASS_MESSAGE,
remove=(3, 14)
)
if not isinstance(inst, Constant): if not isinstance(inst, Constant):
return False return False
if cls in _const_types: if cls in _const_types:
@@ -552,6 +580,10 @@ def _new(cls, *args, **kwargs):
if pos < len(args): if pos < len(args):
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}") raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
if cls in _const_types: if cls in _const_types:
import warnings
warnings._deprecated(
f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return Constant(*args, **kwargs) return Constant(*args, **kwargs)
return Constant.__new__(cls, *args, **kwargs) return Constant.__new__(cls, *args, **kwargs)
@@ -574,10 +606,19 @@ class Ellipsis(Constant, metaclass=_ABC):
_fields = () _fields = ()
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is Ellipsis: if cls is _ast_Ellipsis:
import warnings
warnings._deprecated(
"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return Constant(..., *args, **kwargs) return Constant(..., *args, **kwargs)
return Constant.__new__(cls, *args, **kwargs) return Constant.__new__(cls, *args, **kwargs)
# Keep another reference to Ellipsis in the global namespace
# so it can be referenced in Ellipsis.__new__
# (The original "Ellipsis" name is removed from the global namespace later on)
_ast_Ellipsis = Ellipsis
_const_types = { _const_types = {
Num: (int, float, complex), Num: (int, float, complex),
Str: (str,), Str: (str,),
@@ -644,10 +685,12 @@ class Param(expr_context):
# We unparse those infinities to INFSTR. # We unparse those infinities to INFSTR.
_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) _INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
class _Precedence(IntEnum): @_simple_enum(IntEnum)
class _Precedence:
"""Precedence table that originated from python grammar.""" """Precedence table that originated from python grammar."""
TUPLE = auto() NAMED_EXPR = auto() # <target> := <expr1>
TUPLE = auto() # <expr1>, <expr2>
YIELD = auto() # 'yield', 'yield from' YIELD = auto() # 'yield', 'yield from'
TEST = auto() # 'if'-'else', 'lambda' TEST = auto() # 'if'-'else', 'lambda'
OR = auto() # 'or' OR = auto() # 'or'
@@ -685,11 +728,11 @@ class _Unparser(NodeVisitor):
def __init__(self, *, _avoid_backslashes=False): def __init__(self, *, _avoid_backslashes=False):
self._source = [] self._source = []
self._buffer = []
self._precedences = {} self._precedences = {}
self._type_ignores = {} self._type_ignores = {}
self._indent = 0 self._indent = 0
self._avoid_backslashes = _avoid_backslashes self._avoid_backslashes = _avoid_backslashes
self._in_try_star = False
def interleave(self, inter, f, seq): def interleave(self, inter, f, seq):
"""Call f on each item in seq, calling inter() in between.""" """Call f on each item in seq, calling inter() in between."""
@@ -724,18 +767,19 @@ class _Unparser(NodeVisitor):
self.maybe_newline() self.maybe_newline()
self.write(" " * self._indent + text) self.write(" " * self._indent + text)
def write(self, text): def write(self, *text):
"""Append a piece of text""" """Add new source parts"""
self._source.append(text) self._source.extend(text)
def buffer_writer(self, text): @contextmanager
self._buffer.append(text) def buffered(self, buffer = None):
if buffer is None:
buffer = []
@property original_source = self._source
def buffer(self): self._source = buffer
value = "".join(self._buffer) yield buffer
self._buffer.clear() self._source = original_source
return value
@contextmanager @contextmanager
def block(self, *, extra = None): def block(self, *, extra = None):
@@ -845,7 +889,7 @@ class _Unparser(NodeVisitor):
self.traverse(node.value) self.traverse(node.value)
def visit_NamedExpr(self, node): def visit_NamedExpr(self, node):
with self.require_parens(_Precedence.TUPLE, node): with self.require_parens(_Precedence.NAMED_EXPR, node):
self.set_precedence(_Precedence.ATOM, node.target, node.value) self.set_precedence(_Precedence.ATOM, node.target, node.value)
self.traverse(node.target) self.traverse(node.target)
self.write(" := ") self.write(" := ")
@@ -866,6 +910,7 @@ class _Unparser(NodeVisitor):
def visit_Assign(self, node): def visit_Assign(self, node):
self.fill() self.fill()
for target in node.targets: for target in node.targets:
self.set_precedence(_Precedence.TUPLE, target)
self.traverse(target) self.traverse(target)
self.write(" = ") self.write(" = ")
self.traverse(node.value) self.traverse(node.value)
@@ -958,7 +1003,7 @@ class _Unparser(NodeVisitor):
self.write(" from ") self.write(" from ")
self.traverse(node.cause) self.traverse(node.cause)
def visit_Try(self, node): def do_visit_try(self, node):
self.fill("try") self.fill("try")
with self.block(): with self.block():
self.traverse(node.body) self.traverse(node.body)
@@ -973,8 +1018,24 @@ class _Unparser(NodeVisitor):
with self.block(): with self.block():
self.traverse(node.finalbody) self.traverse(node.finalbody)
def visit_Try(self, node):
prev_in_try_star = self._in_try_star
try:
self._in_try_star = False
self.do_visit_try(node)
finally:
self._in_try_star = prev_in_try_star
def visit_TryStar(self, node):
prev_in_try_star = self._in_try_star
try:
self._in_try_star = True
self.do_visit_try(node)
finally:
self._in_try_star = prev_in_try_star
def visit_ExceptHandler(self, node): def visit_ExceptHandler(self, node):
self.fill("except") self.fill("except*" if self._in_try_star else "except")
if node.type: if node.type:
self.write(" ") self.write(" ")
self.traverse(node.type) self.traverse(node.type)
@@ -990,6 +1051,8 @@ class _Unparser(NodeVisitor):
self.fill("@") self.fill("@")
self.traverse(deco) self.traverse(deco)
self.fill("class " + node.name) self.fill("class " + node.name)
if hasattr(node, "type_params"):
self._type_params_helper(node.type_params)
with self.delimit_if("(", ")", condition = node.bases or node.keywords): with self.delimit_if("(", ")", condition = node.bases or node.keywords):
comma = False comma = False
for e in node.bases: for e in node.bases:
@@ -1021,6 +1084,8 @@ class _Unparser(NodeVisitor):
self.traverse(deco) self.traverse(deco)
def_str = fill_suffix + " " + node.name def_str = fill_suffix + " " + node.name
self.fill(def_str) self.fill(def_str)
if hasattr(node, "type_params"):
self._type_params_helper(node.type_params)
with self.delimit("(", ")"): with self.delimit("(", ")"):
self.traverse(node.args) self.traverse(node.args)
if node.returns: if node.returns:
@@ -1029,6 +1094,30 @@ class _Unparser(NodeVisitor):
with self.block(extra=self.get_type_comment(node)): with self.block(extra=self.get_type_comment(node)):
self._write_docstring_and_traverse_body(node) self._write_docstring_and_traverse_body(node)
def _type_params_helper(self, type_params):
if type_params is not None and len(type_params) > 0:
with self.delimit("[", "]"):
self.interleave(lambda: self.write(", "), self.traverse, type_params)
def visit_TypeVar(self, node):
self.write(node.name)
if node.bound:
self.write(": ")
self.traverse(node.bound)
def visit_TypeVarTuple(self, node):
self.write("*" + node.name)
def visit_ParamSpec(self, node):
self.write("**" + node.name)
def visit_TypeAlias(self, node):
self.fill("type ")
self.traverse(node.name)
self._type_params_helper(node.type_params)
self.write(" = ")
self.traverse(node.value)
def visit_For(self, node): def visit_For(self, node):
self._for_helper("for ", node) self._for_helper("for ", node)
@@ -1037,6 +1126,7 @@ class _Unparser(NodeVisitor):
def _for_helper(self, fill, node): def _for_helper(self, fill, node):
self.fill(fill) self.fill(fill)
self.set_precedence(_Precedence.TUPLE, node.target)
self.traverse(node.target) self.traverse(node.target)
self.write(" in ") self.write(" in ")
self.traverse(node.iter) self.traverse(node.iter)
@@ -1133,71 +1223,81 @@ class _Unparser(NodeVisitor):
def visit_JoinedStr(self, node): def visit_JoinedStr(self, node):
self.write("f") self.write("f")
if self._avoid_backslashes:
self._fstring_JoinedStr(node, self.buffer_writer)
self._write_str_avoiding_backslashes(self.buffer)
return
# If we don't need to avoid backslashes globally (i.e., we only need fstring_parts = []
# to avoid them inside FormattedValues), it's cosmetically preferred
# to use escaped whitespace. That is, it's preferred to use backslashes
# for cases like: f"{x}\n". To accomplish this, we keep track of what
# in our buffer corresponds to FormattedValues and what corresponds to
# Constant parts of the f-string, and allow escapes accordingly.
buffer = []
for value in node.values: for value in node.values:
meth = getattr(self, "_fstring_" + type(value).__name__) with self.buffered() as buffer:
meth(value, self.buffer_writer) self._write_fstring_inner(value)
buffer.append((self.buffer, isinstance(value, Constant))) fstring_parts.append(
new_buffer = [] ("".join(buffer), isinstance(value, Constant))
quote_types = _ALL_QUOTES
for value, is_constant in buffer:
# Repeatedly narrow down the list of possible quote_types
value, quote_types = self._str_literal_helper(
value, quote_types=quote_types,
escape_special_whitespace=is_constant
) )
new_buffer.append(value)
value = "".join(new_buffer) new_fstring_parts = []
quote_types = list(_ALL_QUOTES)
fallback_to_repr = False
for value, is_constant in fstring_parts:
if is_constant:
value, new_quote_types = self._str_literal_helper(
value,
quote_types=quote_types,
escape_special_whitespace=True,
)
if set(new_quote_types).isdisjoint(quote_types):
fallback_to_repr = True
break
quote_types = new_quote_types
elif "\n" in value:
quote_types = [q for q in quote_types if q in _MULTI_QUOTES]
assert quote_types
new_fstring_parts.append(value)
if fallback_to_repr:
# If we weren't able to find a quote type that works for all parts
# of the JoinedStr, fallback to using repr and triple single quotes.
quote_types = ["'''"]
new_fstring_parts.clear()
for value, is_constant in fstring_parts:
if is_constant:
value = repr('"' + value) # force repr to use single quotes
expected_prefix = "'\""
assert value.startswith(expected_prefix), repr(value)
value = value[len(expected_prefix):-1]
new_fstring_parts.append(value)
value = "".join(new_fstring_parts)
quote_type = quote_types[0] quote_type = quote_types[0]
self.write(f"{quote_type}{value}{quote_type}") self.write(f"{quote_type}{value}{quote_type}")
def _write_fstring_inner(self, node):
if isinstance(node, JoinedStr):
# for both the f-string itself, and format_spec
for value in node.values:
self._write_fstring_inner(value)
elif isinstance(node, Constant) and isinstance(node.value, str):
value = node.value.replace("{", "{{").replace("}", "}}")
self.write(value)
elif isinstance(node, FormattedValue):
self.visit_FormattedValue(node)
else:
raise ValueError(f"Unexpected node inside JoinedStr, {node!r}")
def visit_FormattedValue(self, node): def visit_FormattedValue(self, node):
self.write("f") def unparse_inner(inner):
self._fstring_FormattedValue(node, self.buffer_writer) unparser = type(self)()
self._write_str_avoiding_backslashes(self.buffer) unparser.set_precedence(_Precedence.TEST.next(), inner)
return unparser.visit(inner)
def _fstring_JoinedStr(self, node, write): with self.delimit("{", "}"):
for value in node.values: expr = unparse_inner(node.value)
meth = getattr(self, "_fstring_" + type(value).__name__) if expr.startswith("{"):
meth(value, write) # Separate pair of opening brackets as "{ {"
self.write(" ")
def _fstring_Constant(self, node, write): self.write(expr)
if not isinstance(node.value, str): if node.conversion != -1:
raise ValueError("Constants inside JoinedStr should be a string.") self.write(f"!{chr(node.conversion)}")
value = node.value.replace("{", "{{").replace("}", "}}") if node.format_spec:
write(value) self.write(":")
self._write_fstring_inner(node.format_spec)
def _fstring_FormattedValue(self, node, write):
write("{")
unparser = type(self)(_avoid_backslashes=True)
unparser.set_precedence(_Precedence.TEST.next(), node.value)
expr = unparser.visit(node.value)
if expr.startswith("{"):
write(" ") # Separate pair of opening brackets as "{ {"
if "\\" in expr:
raise ValueError("Unable to avoid backslash in f-string expression part")
write(expr)
if node.conversion != -1:
conversion = chr(node.conversion)
if conversion not in "sra":
raise ValueError("Unknown f-string conversion.")
write(f"!{conversion}")
if node.format_spec:
write(":")
meth = getattr(self, "_fstring_" + type(node.format_spec).__name__)
meth(node.format_spec, write)
write("}")
def visit_Name(self, node): def visit_Name(self, node):
self.write(node.id) self.write(node.id)
@@ -1320,7 +1420,11 @@ class _Unparser(NodeVisitor):
) )
def visit_Tuple(self, node): def visit_Tuple(self, node):
with self.delimit("(", ")"): with self.delimit_if(
"(",
")",
len(node.elts) == 0 or self.get_precedence(node) > _Precedence.TUPLE
):
self.items_view(self.traverse, node.elts) self.items_view(self.traverse, node.elts)
unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"} unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}
@@ -1336,7 +1440,7 @@ class _Unparser(NodeVisitor):
operator_precedence = self.unop_precedence[operator] operator_precedence = self.unop_precedence[operator]
with self.require_parens(operator_precedence, node): with self.require_parens(operator_precedence, node):
self.write(operator) self.write(operator)
# factor prefixes (+, -, ~) shouldn't be seperated # factor prefixes (+, -, ~) shouldn't be separated
# from the value they belong, (e.g: +1 instead of + 1) # from the value they belong, (e.g: +1 instead of + 1)
if operator_precedence is not _Precedence.FACTOR: if operator_precedence is not _Precedence.FACTOR:
self.write(" ") self.write(" ")
@@ -1461,20 +1565,17 @@ class _Unparser(NodeVisitor):
self.traverse(e) self.traverse(e)
def visit_Subscript(self, node): def visit_Subscript(self, node):
def is_simple_tuple(slice_value): def is_non_empty_tuple(slice_value):
# when unparsing a non-empty tuple, the parentheses can be safely
# omitted if there aren't any elements that explicitly requires
# parentheses (such as starred expressions).
return ( return (
isinstance(slice_value, Tuple) isinstance(slice_value, Tuple)
and slice_value.elts and slice_value.elts
and not any(isinstance(elt, Starred) for elt in slice_value.elts)
) )
self.set_precedence(_Precedence.ATOM, node.value) self.set_precedence(_Precedence.ATOM, node.value)
self.traverse(node.value) self.traverse(node.value)
with self.delimit("[", "]"): with self.delimit("[", "]"):
if is_simple_tuple(node.slice): if is_non_empty_tuple(node.slice):
# parentheses can be omitted if the tuple isn't empty
self.items_view(self.traverse, node.slice.elts) self.items_view(self.traverse, node.slice.elts)
else: else:
self.traverse(node.slice) self.traverse(node.slice)
@@ -1571,8 +1672,11 @@ class _Unparser(NodeVisitor):
def visit_Lambda(self, node): def visit_Lambda(self, node):
with self.require_parens(_Precedence.TEST, node): with self.require_parens(_Precedence.TEST, node):
self.write("lambda ") self.write("lambda")
self.traverse(node.args) with self.buffered() as buffer:
self.traverse(node.args)
if buffer:
self.write(" ", *buffer)
self.write(": ") self.write(": ")
self.set_precedence(_Precedence.TEST, node.body) self.set_precedence(_Precedence.TEST, node.body)
self.traverse(node.body) self.traverse(node.body)
@@ -1681,6 +1785,22 @@ def unparse(ast_obj):
return unparser.visit(ast_obj) return unparser.visit(ast_obj)
_deprecated_globals = {
name: globals().pop(name)
for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
}
def __getattr__(name):
if name in _deprecated_globals:
globals()[name] = value = _deprecated_globals[name]
import warnings
warnings._deprecated(
f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return value
raise AttributeError(f"module 'ast' has no attribute '{name}'")
def main(): def main():
import argparse import argparse

View File

@@ -1,22 +1,14 @@
"""The asyncio package, tracking PEP 3156.""" """The asyncio package, tracking PEP 3156."""
# flake8: noqa # flake8: noqa
import sys import sys
import selectors
# XXX RustPython TODO: _overlapped
if sys.platform == 'win32' and False:
# Similar thing for _overlapped.
try:
from . import _overlapped
except ImportError:
import _overlapped # Will also be exported.
# This relies on each of the submodules having an __all__ variable. # This relies on each of the submodules having an __all__ variable.
from .base_events import * from .base_events import *
from .coroutines import * from .coroutines import *
from .events import * from .events import *
from .exceptions import *
from .futures import * from .futures import *
from .locks import * from .locks import *
from .protocols import * from .protocols import *
@@ -25,11 +17,15 @@ from .queues import *
from .streams import * from .streams import *
from .subprocess import * from .subprocess import *
from .tasks import * from .tasks import *
from .taskgroups import *
from .timeouts import *
from .threads import *
from .transports import * from .transports import *
__all__ = (base_events.__all__ + __all__ = (base_events.__all__ +
coroutines.__all__ + coroutines.__all__ +
events.__all__ + events.__all__ +
exceptions.__all__ +
futures.__all__ + futures.__all__ +
locks.__all__ + locks.__all__ +
protocols.__all__ + protocols.__all__ +
@@ -38,6 +34,9 @@ __all__ = (base_events.__all__ +
streams.__all__ + streams.__all__ +
subprocess.__all__ + subprocess.__all__ +
tasks.__all__ + tasks.__all__ +
taskgroups.__all__ +
threads.__all__ +
timeouts.__all__ +
transports.__all__) transports.__all__)
if sys.platform == 'win32': # pragma: no cover if sys.platform == 'win32': # pragma: no cover

125
Lib/asyncio/__main__.py vendored Normal file
View File

@@ -0,0 +1,125 @@
import ast
import asyncio
import code
import concurrent.futures
import inspect
import sys
import threading
import types
import warnings
from . import futures
class AsyncIOInteractiveConsole(code.InteractiveConsole):
def __init__(self, locals, loop):
super().__init__(locals)
self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
self.loop = loop
def runcode(self, code):
future = concurrent.futures.Future()
def callback():
global repl_future
global repl_future_interrupted
repl_future = None
repl_future_interrupted = False
func = types.FunctionType(code, self.locals)
try:
coro = func()
except SystemExit:
raise
except KeyboardInterrupt as ex:
repl_future_interrupted = True
future.set_exception(ex)
return
except BaseException as ex:
future.set_exception(ex)
return
if not inspect.iscoroutine(coro):
future.set_result(coro)
return
try:
repl_future = self.loop.create_task(coro)
futures._chain_future(repl_future, future)
except BaseException as exc:
future.set_exception(exc)
loop.call_soon_threadsafe(callback)
try:
return future.result()
except SystemExit:
raise
except BaseException:
if repl_future_interrupted:
self.write("\nKeyboardInterrupt\n")
else:
self.showtraceback()
class REPLThread(threading.Thread):
def run(self):
try:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
f'{getattr(sys, "ps1", ">>> ")}import asyncio'
)
console.interact(
banner=banner,
exitmsg='exiting asyncio REPL...')
finally:
warnings.filterwarnings(
'ignore',
message=r'^coroutine .* was never awaited$',
category=RuntimeWarning)
loop.call_soon_threadsafe(loop.stop)
if __name__ == '__main__':
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
repl_locals = {'asyncio': asyncio}
for key in {'__name__', '__package__',
'__loader__', '__spec__',
'__builtins__', '__file__'}:
repl_locals[key] = locals()[key]
console = AsyncIOInteractiveConsole(repl_locals, loop)
repl_future = None
repl_future_interrupted = False
try:
import readline # NoQA
except ImportError:
pass
repl_thread = REPLThread()
repl_thread.daemon = True
repl_thread.start()
while True:
try:
loop.run_forever()
except KeyboardInterrupt:
if repl_future and not repl_future.done():
repl_future.cancel()
repl_future_interrupted = True
continue
else:
break

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,8 @@
__all__ = [] __all__ = ()
import concurrent.futures._base
import reprlib import reprlib
from . import events from . import format_helpers
Error = concurrent.futures._base.Error
CancelledError = concurrent.futures.CancelledError
TimeoutError = concurrent.futures.TimeoutError
class InvalidStateError(Error):
"""The operation is not allowed in this state."""
# States for Future. # States for Future.
_PENDING = 'PENDING' _PENDING = 'PENDING'
@@ -38,17 +28,17 @@ def _format_callbacks(cb):
cb = '' cb = ''
def format_cb(callback): def format_cb(callback):
return events._format_callback_source(callback, ()) return format_helpers._format_callback_source(callback, ())
if size == 1: if size == 1:
cb = format_cb(cb[0]) cb = format_cb(cb[0][0])
elif size == 2: elif size == 2:
cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1])) cb = '{}, {}'.format(format_cb(cb[0][0]), format_cb(cb[1][0]))
elif size > 2: elif size > 2:
cb = '{}, <{} more>, {}'.format(format_cb(cb[0]), cb = '{}, <{} more>, {}'.format(format_cb(cb[0][0]),
size - 2, size - 2,
format_cb(cb[-1])) format_cb(cb[-1][0]))
return 'cb=[%s]' % cb return f'cb=[{cb}]'
def _future_repr_info(future): def _future_repr_info(future):
@@ -57,15 +47,21 @@ def _future_repr_info(future):
info = [future._state.lower()] info = [future._state.lower()]
if future._state == _FINISHED: if future._state == _FINISHED:
if future._exception is not None: if future._exception is not None:
info.append('exception={!r}'.format(future._exception)) info.append(f'exception={future._exception!r}')
else: else:
# use reprlib to limit the length of the output, especially # use reprlib to limit the length of the output, especially
# for very long strings # for very long strings
result = reprlib.repr(future._result) result = reprlib.repr(future._result)
info.append('result={}'.format(result)) info.append(f'result={result}')
if future._callbacks: if future._callbacks:
info.append(_format_callbacks(future._callbacks)) info.append(_format_callbacks(future._callbacks))
if future._source_traceback: if future._source_traceback:
frame = future._source_traceback[-1] frame = future._source_traceback[-1]
info.append('created at %s:%s' % (frame[0], frame[1])) info.append(f'created at {frame[0]}:{frame[1]}')
return info return info
@reprlib.recursive_repr()
def _future_repr(future):
info = ' '.join(_future_repr_info(future))
return f'<{future.__class__.__name__} {info}>'

View File

@@ -2,10 +2,8 @@ import collections
import subprocess import subprocess
import warnings import warnings
from . import compat
from . import protocols from . import protocols
from . import transports from . import transports
from .coroutines import coroutine
from .log import logger from .log import logger
@@ -59,9 +57,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
if self._closed: if self._closed:
info.append('closed') info.append('closed')
if self._pid is not None: if self._pid is not None:
info.append('pid=%s' % self._pid) info.append(f'pid={self._pid}')
if self._returncode is not None: if self._returncode is not None:
info.append('returncode=%s' % self._returncode) info.append(f'returncode={self._returncode}')
elif self._pid is not None: elif self._pid is not None:
info.append('running') info.append('running')
else: else:
@@ -69,19 +67,19 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
stdin = self._pipes.get(0) stdin = self._pipes.get(0)
if stdin is not None: if stdin is not None:
info.append('stdin=%s' % stdin.pipe) info.append(f'stdin={stdin.pipe}')
stdout = self._pipes.get(1) stdout = self._pipes.get(1)
stderr = self._pipes.get(2) stderr = self._pipes.get(2)
if stdout is not None and stderr is stdout: if stdout is not None and stderr is stdout:
info.append('stdout=stderr=%s' % stdout.pipe) info.append(f'stdout=stderr={stdout.pipe}')
else: else:
if stdout is not None: if stdout is not None:
info.append('stdout=%s' % stdout.pipe) info.append(f'stdout={stdout.pipe}')
if stderr is not None: if stderr is not None:
info.append('stderr=%s' % stderr.pipe) info.append(f'stderr={stderr.pipe}')
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs): def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
raise NotImplementedError raise NotImplementedError
@@ -105,12 +103,13 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
continue continue
proto.pipe.close() proto.pipe.close()
if (self._proc is not None if (self._proc is not None and
# the child process finished? # has the child process finished?
and self._returncode is None self._returncode is None and
# the child process finished but the transport was not notified yet? # the child process has finished, but the
and self._proc.poll() is None # transport hasn't been notified yet?
): self._proc.poll() is None):
if self._loop.get_debug(): if self._loop.get_debug():
logger.warning('Close running child process: kill %r', self) logger.warning('Close running child process: kill %r', self)
@@ -121,15 +120,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
# Don't clear the _proc reference yet: _post_init() may still run # Don't clear the _proc reference yet: _post_init() may still run
# On Python 3.3 and older, objects with a destructor part of a reference def __del__(self, _warn=warnings.warn):
# cycle are never destroyed. It's not more the case on Python 3.4 thanks if not self._closed:
# to the PEP 442. _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
if compat.PY34: self.close()
def __del__(self):
if not self._closed:
warnings.warn("unclosed transport %r" % self, ResourceWarning,
source=self)
self.close()
def get_pid(self): def get_pid(self):
return self._pid return self._pid
@@ -159,26 +153,25 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
self._check_proc() self._check_proc()
self._proc.kill() self._proc.kill()
@coroutine async def _connect_pipes(self, waiter):
def _connect_pipes(self, waiter):
try: try:
proc = self._proc proc = self._proc
loop = self._loop loop = self._loop
if proc.stdin is not None: if proc.stdin is not None:
_, pipe = yield from loop.connect_write_pipe( _, pipe = await loop.connect_write_pipe(
lambda: WriteSubprocessPipeProto(self, 0), lambda: WriteSubprocessPipeProto(self, 0),
proc.stdin) proc.stdin)
self._pipes[0] = pipe self._pipes[0] = pipe
if proc.stdout is not None: if proc.stdout is not None:
_, pipe = yield from loop.connect_read_pipe( _, pipe = await loop.connect_read_pipe(
lambda: ReadSubprocessPipeProto(self, 1), lambda: ReadSubprocessPipeProto(self, 1),
proc.stdout) proc.stdout)
self._pipes[1] = pipe self._pipes[1] = pipe
if proc.stderr is not None: if proc.stderr is not None:
_, pipe = yield from loop.connect_read_pipe( _, pipe = await loop.connect_read_pipe(
lambda: ReadSubprocessPipeProto(self, 2), lambda: ReadSubprocessPipeProto(self, 2),
proc.stderr) proc.stderr)
self._pipes[2] = pipe self._pipes[2] = pipe
@@ -189,7 +182,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
for callback, data in self._pending_calls: for callback, data in self._pending_calls:
loop.call_soon(callback, *data) loop.call_soon(callback, *data)
self._pending_calls = None self._pending_calls = None
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if waiter is not None and not waiter.cancelled(): if waiter is not None and not waiter.cancelled():
waiter.set_exception(exc) waiter.set_exception(exc)
else: else:
@@ -213,24 +208,17 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
assert returncode is not None, returncode assert returncode is not None, returncode
assert self._returncode is None, self._returncode assert self._returncode is None, self._returncode
if self._loop.get_debug(): if self._loop.get_debug():
logger.info('%r exited with return code %r', logger.info('%r exited with return code %r', self, returncode)
self, returncode)
self._returncode = returncode self._returncode = returncode
if self._proc.returncode is None: if self._proc.returncode is None:
# asyncio uses a child watcher: copy the status into the Popen # asyncio uses a child watcher: copy the status into the Popen
# object. On Python 3.6, it is required to avoid a ResourceWarning. # object. On Python 3.6, it is required to avoid a ResourceWarning.
self._proc.returncode = returncode self._proc.returncode = returncode
self._call(self._protocol.process_exited) self._call(self._protocol.process_exited)
self._try_finish() self._try_finish()
# wake up futures waiting for wait() async def _wait(self):
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(returncode)
self._exit_waiters = None
@coroutine
def _wait(self):
"""Wait until the process exit and return the process return code. """Wait until the process exit and return the process return code.
This method is a coroutine.""" This method is a coroutine."""
@@ -239,7 +227,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
waiter = self._loop.create_future() waiter = self._loop.create_future()
self._exit_waiters.append(waiter) self._exit_waiters.append(waiter)
return (yield from waiter) return await waiter
def _try_finish(self): def _try_finish(self):
assert not self._finished assert not self._finished
@@ -254,6 +242,11 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
try: try:
self._protocol.connection_lost(exc) self._protocol.connection_lost(exc)
finally: finally:
# wake up futures waiting for wait()
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(self._returncode)
self._exit_waiters = None
self._loop = None self._loop = None
self._proc = None self._proc = None
self._protocol = None self._protocol = None
@@ -271,8 +264,7 @@ class WriteSubprocessPipeProto(protocols.BaseProtocol):
self.pipe = transport self.pipe = transport
def __repr__(self): def __repr__(self):
return ('<%s fd=%s pipe=%r>' return f'<{self.__class__.__name__} fd={self.fd} pipe={self.pipe!r}>'
% (self.__class__.__name__, self.fd, self.pipe))
def connection_lost(self, exc): def connection_lost(self, exc):
self.disconnected = True self.disconnected = True

View File

@@ -1,4 +1,5 @@
import linecache import linecache
import reprlib
import traceback import traceback
from . import base_futures from . import base_futures
@@ -8,25 +9,42 @@ from . import coroutines
def _task_repr_info(task): def _task_repr_info(task):
info = base_futures._future_repr_info(task) info = base_futures._future_repr_info(task)
if task._must_cancel: if task.cancelling() and not task.done():
# replace status # replace status
info[0] = 'cancelling' info[0] = 'cancelling'
coro = coroutines._format_coroutine(task._coro) info.insert(1, 'name=%r' % task.get_name())
info.insert(1, 'coro=<%s>' % coro)
if task._fut_waiter is not None: if task._fut_waiter is not None:
info.insert(2, 'wait_for=%r' % task._fut_waiter) info.insert(2, f'wait_for={task._fut_waiter!r}')
if task._coro:
coro = coroutines._format_coroutine(task._coro)
info.insert(2, f'coro=<{coro}>')
return info return info
@reprlib.recursive_repr()
def _task_repr(task):
info = ' '.join(_task_repr_info(task))
return f'<{task.__class__.__name__} {info}>'
def _task_get_stack(task, limit): def _task_get_stack(task, limit):
frames = [] frames = []
try: if hasattr(task._coro, 'cr_frame'):
# 'async def' coroutines # case 1: 'async def' coroutines
f = task._coro.cr_frame f = task._coro.cr_frame
except AttributeError: elif hasattr(task._coro, 'gi_frame'):
# case 2: legacy coroutines
f = task._coro.gi_frame f = task._coro.gi_frame
elif hasattr(task._coro, 'ag_frame'):
# case 3: async generators
f = task._coro.ag_frame
else:
# case 4: unknown objects
f = None
if f is not None: if f is not None:
while f is not None: while f is not None:
if limit is not None: if limit is not None:
@@ -61,15 +79,15 @@ def _task_print_stack(task, limit, file):
linecache.checkcache(filename) linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals) line = linecache.getline(filename, lineno, f.f_globals)
extracted_list.append((filename, lineno, name, line)) extracted_list.append((filename, lineno, name, line))
exc = task._exception exc = task._exception
if not extracted_list: if not extracted_list:
print('No stack for %r' % task, file=file) print(f'No stack for {task!r}', file=file)
elif exc is not None: elif exc is not None:
print('Traceback for %r (most recent call last):' % task, print(f'Traceback for {task!r} (most recent call last):', file=file)
file=file)
else: else:
print('Stack for %r (most recent call last):' % task, print(f'Stack for {task!r} (most recent call last):', file=file)
file=file)
traceback.print_list(extracted_list, file=file) traceback.print_list(extracted_list, file=file)
if exc is not None: if exc is not None:
for line in traceback.format_exception_only(exc.__class__, exc): for line in traceback.format_exception_only(exc.__class__, exc):

18
Lib/asyncio/compat.py vendored
View File

@@ -1,18 +0,0 @@
"""Compatibility helpers for the different Python versions."""
import sys
PY34 = sys.version_info >= (3, 4)
PY35 = sys.version_info >= (3, 5)
PY352 = sys.version_info >= (3, 5, 2)
def flatten_list_bytes(list_of_data):
"""Concatenate a sequence of bytes-like objects."""
if not PY34:
# On Python 3.3 and older, bytes.join() doesn't handle
# memoryview.
list_of_data = (
bytes(data) if isinstance(data, memoryview) else data
for data in list_of_data)
return b''.join(list_of_data)

View File

@@ -1,7 +1,41 @@
"""Constants.""" # Contains code from https://github.com/MagicStack/uvloop/tree/v0.16.0
# SPDX-License-Identifier: PSF-2.0 AND (MIT OR Apache-2.0)
# SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc. http://magic.io
import enum
# After the connection is lost, log warnings after this many write()s. # After the connection is lost, log warnings after this many write()s.
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
# Seconds to wait before retrying accept(). # Seconds to wait before retrying accept().
ACCEPT_RETRY_DELAY = 1 ACCEPT_RETRY_DELAY = 1
# Number of stack entries to capture in debug mode.
# The larger the number, the slower the operation in debug mode
# (see extract_stack() in format_helpers.py).
DEBUG_STACK_DEPTH = 10
# Number of seconds to wait for SSL handshake to complete
# The default timeout matches that of Nginx.
SSL_HANDSHAKE_TIMEOUT = 60.0
# Number of seconds to wait for SSL shutdown to complete
# The default timeout mimics lingering_time
SSL_SHUTDOWN_TIMEOUT = 30.0
# Used in sendfile fallback code. We use fallback for platforms
# that don't support sendfile, or for TLS connections.
SENDFILE_FALLBACK_READBUFFER_SIZE = 1024 * 256
FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB
FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB
# Default timeout for joining the threads in the threadpool
THREAD_JOIN_TIMEOUT = 300
# The enum should be here to break circular dependencies between
# base_events and sslproto
class _SendfileMode(enum.Enum):
UNSUPPORTED = enum.auto()
TRY_NATIVE = enum.auto()
FALLBACK = enum.auto()

View File

@@ -1,249 +1,16 @@
__all__ = ['coroutine', __all__ = 'iscoroutinefunction', 'iscoroutine'
'iscoroutinefunction', 'iscoroutine']
import functools import collections.abc
import inspect import inspect
import opcode
import os import os
import sys import sys
import traceback
import types import types
from . import compat
from . import events
from . import base_futures
from .log import logger
def _is_debug_mode():
# Opcode of "yield from" instruction # See: https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode.
_YIELD_FROM = opcode.opmap['YIELD_FROM'] return sys.flags.dev_mode or (not sys.flags.ignore_environment and
bool(os.environ.get('PYTHONASYNCIODEBUG')))
# If you set _DEBUG to true, @coroutine will wrap the resulting
# generator objects in a CoroWrapper instance (defined below). That
# instance will log a message when the generator is never iterated
# over, which may happen when you forget to use "yield from" with a
# coroutine call. Note that the value of the _DEBUG flag is taken
# when the decorator is used, so to be of any use it must be set
# before you define your coroutines. A downside of using this feature
# is that tracebacks show entries for the CoroWrapper.__next__ method
# when _DEBUG is true.
_DEBUG = (not sys.flags.ignore_environment and
bool(os.environ.get('PYTHONASYNCIODEBUG')))
try:
_types_coroutine = types.coroutine
_types_CoroutineType = types.CoroutineType
except AttributeError:
# Python 3.4
_types_coroutine = None
_types_CoroutineType = None
try:
_inspect_iscoroutinefunction = inspect.iscoroutinefunction
except AttributeError:
# Python 3.4
_inspect_iscoroutinefunction = lambda func: False
try:
from collections.abc import Coroutine as _CoroutineABC, \
Awaitable as _AwaitableABC
except ImportError:
_CoroutineABC = _AwaitableABC = None
# Check for CPython issue #21209
def has_yield_from_bug():
class MyGen:
def __init__(self):
self.send_args = None
def __iter__(self):
return self
def __next__(self):
return 42
def send(self, *what):
self.send_args = what
return None
def yield_from_gen(gen):
yield from gen
value = (1, 2, 3)
gen = MyGen()
coro = yield_from_gen(gen)
next(coro)
coro.send(value)
return gen.send_args != (value,)
_YIELD_FROM_BUG = has_yield_from_bug()
del has_yield_from_bug
def debug_wrapper(gen):
# This function is called from 'sys.set_coroutine_wrapper'.
# We only wrap here coroutines defined via 'async def' syntax.
# Generator-based coroutines are wrapped in @coroutine
# decorator.
return CoroWrapper(gen, None)
class CoroWrapper:
# Wrapper for coroutine object in _DEBUG mode.
def __init__(self, gen, func=None):
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
self.gen = gen
self.func = func # Used to unwrap @coroutine decorator
self._source_traceback = traceback.extract_stack(sys._getframe(1))
self.__name__ = getattr(gen, '__name__', None)
self.__qualname__ = getattr(gen, '__qualname__', None)
def __repr__(self):
coro_repr = _format_coroutine(self)
if self._source_traceback:
frame = self._source_traceback[-1]
coro_repr += ', created at %s:%s' % (frame[0], frame[1])
return '<%s %s>' % (self.__class__.__name__, coro_repr)
def __iter__(self):
return self
def __next__(self):
return self.gen.send(None)
if _YIELD_FROM_BUG:
# For for CPython issue #21209: using "yield from" and a custom
# generator, generator.send(tuple) unpacks the tuple instead of passing
# the tuple unchanged. Check if the caller is a generator using "yield
# from" to decide if the parameter should be unpacked or not.
def send(self, *value):
frame = sys._getframe()
caller = frame.f_back
assert caller.f_lasti >= 0
if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
value = value[0]
return self.gen.send(value)
else:
def send(self, value):
return self.gen.send(value)
def throw(self, type, value=None, traceback=None):
return self.gen.throw(type, value, traceback)
def close(self):
return self.gen.close()
@property
def gi_frame(self):
return self.gen.gi_frame
@property
def gi_running(self):
return self.gen.gi_running
@property
def gi_code(self):
return self.gen.gi_code
if compat.PY35:
def __await__(self):
cr_await = getattr(self.gen, 'cr_await', None)
if cr_await is not None:
raise RuntimeError(
"Cannot await on coroutine {!r} while it's "
"awaiting for {!r}".format(self.gen, cr_await))
return self
@property
def gi_yieldfrom(self):
return self.gen.gi_yieldfrom
@property
def cr_await(self):
return self.gen.cr_await
@property
def cr_running(self):
return self.gen.cr_running
@property
def cr_code(self):
return self.gen.cr_code
@property
def cr_frame(self):
return self.gen.cr_frame
def __del__(self):
# Be careful accessing self.gen.frame -- self.gen might not exist.
gen = getattr(self, 'gen', None)
frame = getattr(gen, 'gi_frame', None)
if frame is None:
frame = getattr(gen, 'cr_frame', None)
if frame is not None and frame.f_lasti == -1:
msg = '%r was never yielded from' % self
tb = getattr(self, '_source_traceback', ())
if tb:
tb = ''.join(traceback.format_list(tb))
msg += ('\nCoroutine object created at '
'(most recent call last):\n')
msg += tb.rstrip()
logger.error(msg)
def coroutine(func):
"""Decorator to mark coroutines.
If the coroutine is not yielded from before it is destroyed,
an error message is logged.
"""
if _inspect_iscoroutinefunction(func):
# In Python 3.5 that's all we need to do for coroutines
# defiend with "async def".
# Wrapping in CoroWrapper will happen via
# 'sys.set_coroutine_wrapper' function.
return func
if inspect.isgeneratorfunction(func):
coro = func
else:
@functools.wraps(func)
def coro(*args, **kw):
res = func(*args, **kw)
if (base_futures.isfuture(res) or inspect.isgenerator(res) or
isinstance(res, CoroWrapper)):
res = yield from res
elif _AwaitableABC is not None:
# If 'func' returns an Awaitable (new in 3.5) we
# want to run it.
try:
await_meth = res.__await__
except AttributeError:
pass
else:
if isinstance(res, _AwaitableABC):
res = yield from await_meth()
return res
if not _DEBUG:
if _types_coroutine is None:
wrapper = coro
else:
wrapper = _types_coroutine(coro)
else:
@functools.wraps(func)
def wrapper(*args, **kwds):
w = CoroWrapper(coro(*args, **kwds), func=func)
if w._source_traceback:
del w._source_traceback[-1]
# Python < 3.5 does not implement __qualname__
# on generator objects, so we set it manually.
# We use getattr as some callables (such as
# functools.partial may lack __qualname__).
w.__name__ = getattr(func, '__name__', None)
w.__qualname__ = getattr(func, '__qualname__', None)
return w
wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction().
return wrapper
# A marker for iscoroutinefunction. # A marker for iscoroutinefunction.
@@ -252,93 +19,91 @@ _is_coroutine = object()
def iscoroutinefunction(func): def iscoroutinefunction(func):
"""Return True if func is a decorated coroutine function.""" """Return True if func is a decorated coroutine function."""
return (getattr(func, '_is_coroutine', None) is _is_coroutine or return (inspect.iscoroutinefunction(func) or
_inspect_iscoroutinefunction(func)) getattr(func, '_is_coroutine', None) is _is_coroutine)
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper) # Prioritize native coroutine check to speed-up
if _CoroutineABC is not None: # asyncio.iscoroutine.
_COROUTINE_TYPES += (_CoroutineABC,) _COROUTINE_TYPES = (types.CoroutineType, collections.abc.Coroutine)
if _types_CoroutineType is not None: _iscoroutine_typecache = set()
# Prioritize native coroutine check to speed-up
# asyncio.iscoroutine.
_COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES
def iscoroutine(obj): def iscoroutine(obj):
"""Return True if obj is a coroutine object.""" """Return True if obj is a coroutine object."""
return isinstance(obj, _COROUTINE_TYPES) if type(obj) in _iscoroutine_typecache:
return True
if isinstance(obj, _COROUTINE_TYPES):
# Just in case we don't want to cache more than 100
# positive types. That shouldn't ever happen, unless
# someone stressing the system on purpose.
if len(_iscoroutine_typecache) < 100:
_iscoroutine_typecache.add(type(obj))
return True
else:
return False
def _format_coroutine(coro): def _format_coroutine(coro):
assert iscoroutine(coro) assert iscoroutine(coro)
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'): def get_name(coro):
# Most likely a built-in type or a Cython coroutine. # Coroutines compiled with Cython sometimes don't have
# proper __qualname__ or __name__. While that is a bug
# in Cython, asyncio shouldn't crash with an AttributeError
# in its __repr__ functions.
if hasattr(coro, '__qualname__') and coro.__qualname__:
coro_name = coro.__qualname__
elif hasattr(coro, '__name__') and coro.__name__:
coro_name = coro.__name__
else:
# Stop masking Cython bugs, expose them in a friendly way.
coro_name = f'<{type(coro).__name__} without __name__>'
return f'{coro_name}()'
# Built-in types might not have __qualname__ or __name__. def is_running(coro):
coro_name = getattr(
coro, '__qualname__',
getattr(coro, '__name__', type(coro).__name__))
coro_name = '{}()'.format(coro_name)
running = False
try: try:
running = coro.cr_running return coro.cr_running
except AttributeError: except AttributeError:
try: try:
running = coro.gi_running return coro.gi_running
except AttributeError: except AttributeError:
pass return False
if running: coro_code = None
return '{} running'.format(coro_name) if hasattr(coro, 'cr_code') and coro.cr_code:
coro_code = coro.cr_code
elif hasattr(coro, 'gi_code') and coro.gi_code:
coro_code = coro.gi_code
coro_name = get_name(coro)
if not coro_code:
# Built-in types might not have __qualname__ or __name__.
if is_running(coro):
return f'{coro_name} running'
else: else:
return coro_name return coro_name
coro_name = None coro_frame = None
if isinstance(coro, CoroWrapper): if hasattr(coro, 'gi_frame') and coro.gi_frame:
func = coro.func
coro_name = coro.__qualname__
if coro_name is not None:
coro_name = '{}()'.format(coro_name)
else:
func = coro
if coro_name is None:
coro_name = events._format_callback(func, (), {})
try:
coro_code = coro.gi_code
except AttributeError:
coro_code = coro.cr_code
try:
coro_frame = coro.gi_frame coro_frame = coro.gi_frame
except AttributeError: elif hasattr(coro, 'cr_frame') and coro.cr_frame:
coro_frame = coro.cr_frame coro_frame = coro.cr_frame
filename = coro_code.co_filename # If Cython's coroutine has a fake code object without proper
# co_filename -- expose that.
filename = coro_code.co_filename or '<empty co_filename>'
lineno = 0 lineno = 0
if (isinstance(coro, CoroWrapper) and
not inspect.isgeneratorfunction(coro.func) and if coro_frame is not None:
coro.func is not None):
source = events._get_function_source(coro.func)
if source is not None:
filename, lineno = source
if coro_frame is None:
coro_repr = ('%s done, defined at %s:%s'
% (coro_name, filename, lineno))
else:
coro_repr = ('%s running, defined at %s:%s'
% (coro_name, filename, lineno))
elif coro_frame is not None:
lineno = coro_frame.f_lineno lineno = coro_frame.f_lineno
coro_repr = ('%s running at %s:%s' coro_repr = f'{coro_name} running at {filename}:{lineno}'
% (coro_name, filename, lineno))
else: else:
lineno = coro_code.co_firstlineno lineno = coro_code.co_firstlineno
coro_repr = ('%s done, defined at %s:%s' coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
% (coro_name, filename, lineno))
return coro_repr return coro_repr

469
Lib/asyncio/events.py vendored
View File

@@ -1,96 +1,50 @@
"""Event loop and event loop policy.""" """Event loop and event loop policy."""
__all__ = ['AbstractEventLoopPolicy', # Contains code from https://github.com/MagicStack/uvloop/tree/v0.16.0
'AbstractEventLoop', 'AbstractServer', # SPDX-License-Identifier: PSF-2.0 AND (MIT OR Apache-2.0)
'Handle', 'TimerHandle', # SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc. http://magic.io
'get_event_loop_policy', 'set_event_loop_policy',
'get_event_loop', 'set_event_loop', 'new_event_loop',
'get_child_watcher', 'set_child_watcher',
'_set_running_loop', 'get_running_loop',
'_get_running_loop',
]
import functools __all__ = (
import inspect 'AbstractEventLoopPolicy',
import reprlib 'AbstractEventLoop', 'AbstractServer',
'Handle', 'TimerHandle',
'get_event_loop_policy', 'set_event_loop_policy',
'get_event_loop', 'set_event_loop', 'new_event_loop',
'get_child_watcher', 'set_child_watcher',
'_set_running_loop', 'get_running_loop',
'_get_running_loop',
)
import contextvars
import os
import signal
import socket import socket
import subprocess import subprocess
import sys import sys
import threading import threading
import traceback
from asyncio import compat from . import format_helpers
def _get_function_source(func):
if compat.PY34:
func = inspect.unwrap(func)
elif hasattr(func, '__wrapped__'):
func = func.__wrapped__
if inspect.isfunction(func):
code = func.__code__
return (code.co_filename, code.co_firstlineno)
if isinstance(func, functools.partial):
return _get_function_source(func.func)
if compat.PY34 and isinstance(func, functools.partialmethod):
return _get_function_source(func.func)
return None
def _format_args_and_kwargs(args, kwargs):
"""Format function arguments and keyword arguments.
Special case for a single parameter: ('hello',) is formatted as ('hello').
"""
# use reprlib to limit the length of the output
items = []
if args:
items.extend(reprlib.repr(arg) for arg in args)
if kwargs:
items.extend('{}={}'.format(k, reprlib.repr(v))
for k, v in kwargs.items())
return '(' + ', '.join(items) + ')'
def _format_callback(func, args, kwargs, suffix=''):
if isinstance(func, functools.partial):
suffix = _format_args_and_kwargs(args, kwargs) + suffix
return _format_callback(func.func, func.args, func.keywords, suffix)
if hasattr(func, '__qualname__'):
func_repr = getattr(func, '__qualname__')
elif hasattr(func, '__name__'):
func_repr = getattr(func, '__name__')
else:
func_repr = repr(func)
func_repr += _format_args_and_kwargs(args, kwargs)
if suffix:
func_repr += suffix
return func_repr
def _format_callback_source(func, args):
func_repr = _format_callback(func, args, None)
source = _get_function_source(func)
if source:
func_repr += ' at %s:%s' % source
return func_repr
class Handle: class Handle:
"""Object returned by callback registration methods.""" """Object returned by callback registration methods."""
__slots__ = ('_callback', '_args', '_cancelled', '_loop', __slots__ = ('_callback', '_args', '_cancelled', '_loop',
'_source_traceback', '_repr', '__weakref__') '_source_traceback', '_repr', '__weakref__',
'_context')
def __init__(self, callback, args, loop): def __init__(self, callback, args, loop, context=None):
if context is None:
context = contextvars.copy_context()
self._context = context
self._loop = loop self._loop = loop
self._callback = callback self._callback = callback
self._args = args self._args = args
self._cancelled = False self._cancelled = False
self._repr = None self._repr = None
if self._loop.get_debug(): if self._loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1)) self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
else: else:
self._source_traceback = None self._source_traceback = None
@@ -99,17 +53,21 @@ class Handle:
if self._cancelled: if self._cancelled:
info.append('cancelled') info.append('cancelled')
if self._callback is not None: if self._callback is not None:
info.append(_format_callback_source(self._callback, self._args)) info.append(format_helpers._format_callback_source(
self._callback, self._args))
if self._source_traceback: if self._source_traceback:
frame = self._source_traceback[-1] frame = self._source_traceback[-1]
info.append('created at %s:%s' % (frame[0], frame[1])) info.append(f'created at {frame[0]}:{frame[1]}')
return info return info
def __repr__(self): def __repr__(self):
if self._repr is not None: if self._repr is not None:
return self._repr return self._repr
info = self._repr_info() info = self._repr_info()
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
def get_context(self):
return self._context
def cancel(self): def cancel(self):
if not self._cancelled: if not self._cancelled:
@@ -122,12 +80,18 @@ class Handle:
self._callback = None self._callback = None
self._args = None self._args = None
def cancelled(self):
return self._cancelled
def _run(self): def _run(self):
try: try:
self._callback(*self._args) self._context.run(self._callback, *self._args)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
cb = _format_callback_source(self._callback, self._args) raise
msg = 'Exception in callback {}'.format(cb) except BaseException as exc:
cb = format_helpers._format_callback_source(
self._callback, self._args)
msg = f'Exception in callback {cb}'
context = { context = {
'message': msg, 'message': msg,
'exception': exc, 'exception': exc,
@@ -144,9 +108,8 @@ class TimerHandle(Handle):
__slots__ = ['_scheduled', '_when'] __slots__ = ['_scheduled', '_when']
def __init__(self, when, callback, args, loop): def __init__(self, when, callback, args, loop, context=None):
assert when is not None super().__init__(callback, args, loop, context)
super().__init__(callback, args, loop)
if self._source_traceback: if self._source_traceback:
del self._source_traceback[-1] del self._source_traceback[-1]
self._when = when self._when = when
@@ -155,27 +118,31 @@ class TimerHandle(Handle):
def _repr_info(self): def _repr_info(self):
info = super()._repr_info() info = super()._repr_info()
pos = 2 if self._cancelled else 1 pos = 2 if self._cancelled else 1
info.insert(pos, 'when=%s' % self._when) info.insert(pos, f'when={self._when}')
return info return info
def __hash__(self): def __hash__(self):
return hash(self._when) return hash(self._when)
def __lt__(self, other): def __lt__(self, other):
return self._when < other._when if isinstance(other, TimerHandle):
return self._when < other._when
return NotImplemented
def __le__(self, other): def __le__(self, other):
if self._when < other._when: if isinstance(other, TimerHandle):
return True return self._when < other._when or self.__eq__(other)
return self.__eq__(other) return NotImplemented
def __gt__(self, other): def __gt__(self, other):
return self._when > other._when if isinstance(other, TimerHandle):
return self._when > other._when
return NotImplemented
def __ge__(self, other): def __ge__(self, other):
if self._when > other._when: if isinstance(other, TimerHandle):
return True return self._when > other._when or self.__eq__(other)
return self.__eq__(other) return NotImplemented
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, TimerHandle): if isinstance(other, TimerHandle):
@@ -185,26 +152,60 @@ class TimerHandle(Handle):
self._cancelled == other._cancelled) self._cancelled == other._cancelled)
return NotImplemented return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def cancel(self): def cancel(self):
if not self._cancelled: if not self._cancelled:
self._loop._timer_handle_cancelled(self) self._loop._timer_handle_cancelled(self)
super().cancel() super().cancel()
def when(self):
"""Return a scheduled callback time.
The time is an absolute timestamp, using the same time
reference as loop.time().
"""
return self._when
class AbstractServer: class AbstractServer:
"""Abstract server returned by create_server().""" """Abstract server returned by create_server()."""
def close(self): def close(self):
"""Stop serving. This leaves existing connections open.""" """Stop serving. This leaves existing connections open."""
return NotImplemented raise NotImplementedError
def wait_closed(self): def get_loop(self):
"""Get the event loop the Server object is attached to."""
raise NotImplementedError
def is_serving(self):
"""Return True if the server is accepting connections."""
raise NotImplementedError
async def start_serving(self):
"""Start accepting connections.
This method is idempotent, so it can be called when
the server is already being serving.
"""
raise NotImplementedError
async def serve_forever(self):
"""Start accepting connections until the coroutine is cancelled.
The server is closed when the coroutine is cancelled.
"""
raise NotImplementedError
async def wait_closed(self):
"""Coroutine to wait until service is closed.""" """Coroutine to wait until service is closed."""
return NotImplemented raise NotImplementedError
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
self.close()
await self.wait_closed()
class AbstractEventLoop: class AbstractEventLoop:
@@ -250,23 +251,27 @@ class AbstractEventLoop:
""" """
raise NotImplementedError raise NotImplementedError
def shutdown_asyncgens(self): async def shutdown_asyncgens(self):
"""Shutdown all active asynchronous generators.""" """Shutdown all active asynchronous generators."""
raise NotImplementedError raise NotImplementedError
async def shutdown_default_executor(self):
"""Schedule the shutdown of the default executor."""
raise NotImplementedError
# Methods scheduling callbacks. All these return Handles. # Methods scheduling callbacks. All these return Handles.
def _timer_handle_cancelled(self, handle): def _timer_handle_cancelled(self, handle):
"""Notification that a TimerHandle has been cancelled.""" """Notification that a TimerHandle has been cancelled."""
raise NotImplementedError raise NotImplementedError
def call_soon(self, callback, *args): def call_soon(self, callback, *args, context=None):
return self.call_later(0, callback, *args) return self.call_later(0, callback, *args, context=context)
def call_later(self, delay, callback, *args): def call_later(self, delay, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
def call_at(self, when, callback, *args): def call_at(self, when, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
def time(self): def time(self):
@@ -277,12 +282,12 @@ class AbstractEventLoop:
# Method scheduling a coroutine object: create a task. # Method scheduling a coroutine object: create a task.
def create_task(self, coro): def create_task(self, coro, *, name=None, context=None):
raise NotImplementedError raise NotImplementedError
# Methods for interacting with threads. # Methods for interacting with threads.
def call_soon_threadsafe(self, callback, *args): def call_soon_threadsafe(self, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
def run_in_executor(self, executor, func, *args): def run_in_executor(self, executor, func, *args):
@@ -293,21 +298,31 @@ class AbstractEventLoop:
# Network I/O methods returning Futures. # Network I/O methods returning Futures.
def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): async def getaddrinfo(self, host, port, *,
family=0, type=0, proto=0, flags=0):
raise NotImplementedError raise NotImplementedError
def getnameinfo(self, sockaddr, flags=0): async def getnameinfo(self, sockaddr, flags=0):
raise NotImplementedError raise NotImplementedError
def create_connection(self, protocol_factory, host=None, port=None, *, async def create_connection(
ssl=None, family=0, proto=0, flags=0, sock=None, self, protocol_factory, host=None, port=None,
local_addr=None, server_hostname=None): *, ssl=None, family=0, proto=0,
flags=0, sock=None, local_addr=None,
server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
happy_eyeballs_delay=None, interleave=None):
raise NotImplementedError raise NotImplementedError
def create_server(self, protocol_factory, host=None, port=None, *, async def create_server(
family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, self, protocol_factory, host=None, port=None,
sock=None, backlog=100, ssl=None, reuse_address=None, *, family=socket.AF_UNSPEC,
reuse_port=None): flags=socket.AI_PASSIVE, sock=None, backlog=100,
ssl=None, reuse_address=None, reuse_port=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
start_serving=True):
"""A coroutine which creates a TCP server bound to host and port. """A coroutine which creates a TCP server bound to host and port.
The return value is a Server object which can be used to stop The return value is a Server object which can be used to stop
@@ -315,8 +330,8 @@ class AbstractEventLoop:
If host is an empty string or None all interfaces are assumed If host is an empty string or None all interfaces are assumed
and a list of multiple sockets will be returned (most likely and a list of multiple sockets will be returned (most likely
one for IPv4 and another one for IPv6). The host parameter can also be a one for IPv4 and another one for IPv6). The host parameter can also be
sequence (e.g. list) of hosts to bind to. a sequence (e.g. list) of hosts to bind to.
family can be set to either AF_INET or AF_INET6 to force the family can be set to either AF_INET or AF_INET6 to force the
socket to use IPv4 or IPv6. If not set it will be determined socket to use IPv4 or IPv6. If not set it will be determined
@@ -342,22 +357,62 @@ class AbstractEventLoop:
the same port as other existing endpoints are bound to, so long as the same port as other existing endpoints are bound to, so long as
they all set this flag when being created. This option is not they all set this flag when being created. This option is not
supported on Windows. supported on Windows.
ssl_handshake_timeout is the time in seconds that an SSL server
will wait for completion of the SSL handshake before aborting the
connection. Default is 60s.
ssl_shutdown_timeout is the time in seconds that an SSL server
will wait for completion of the SSL shutdown procedure
before aborting the connection. Default is 30s.
start_serving set to True (default) causes the created server
to start accepting connections immediately. When set to False,
the user should await Server.start_serving() or Server.serve_forever()
to make the server to start accepting connections.
""" """
raise NotImplementedError raise NotImplementedError
def create_unix_connection(self, protocol_factory, path, *, async def sendfile(self, transport, file, offset=0, count=None,
ssl=None, sock=None, *, fallback=True):
server_hostname=None): """Send a file through a transport.
Return an amount of sent bytes.
"""
raise NotImplementedError raise NotImplementedError
def create_unix_server(self, protocol_factory, path, *, async def start_tls(self, transport, protocol, sslcontext, *,
sock=None, backlog=100, ssl=None): server_side=False,
server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
"""Upgrade a transport to TLS.
Return a new transport that *protocol* should start using
immediately.
"""
raise NotImplementedError
async def create_unix_connection(
self, protocol_factory, path=None, *,
ssl=None, sock=None,
server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
raise NotImplementedError
async def create_unix_server(
self, protocol_factory, path=None, *,
sock=None, backlog=100, ssl=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
start_serving=True):
"""A coroutine which creates a UNIX Domain Socket server. """A coroutine which creates a UNIX Domain Socket server.
The return value is a Server object, which can be used to stop The return value is a Server object, which can be used to stop
the service. the service.
path is a str, representing a file systsem path to bind the path is a str, representing a file system path to bind the
server socket to. server socket to.
sock can optionally be specified in order to use a preexisting sock can optionally be specified in order to use a preexisting
@@ -368,14 +423,40 @@ class AbstractEventLoop:
ssl can be set to an SSLContext to enable SSL over the ssl can be set to an SSLContext to enable SSL over the
accepted connections. accepted connections.
ssl_handshake_timeout is the time in seconds that an SSL server
will wait for the SSL handshake to complete (defaults to 60s).
ssl_shutdown_timeout is the time in seconds that an SSL server
will wait for the SSL shutdown to finish (defaults to 30s).
start_serving set to True (default) causes the created server
to start accepting connections immediately. When set to False,
the user should await Server.start_serving() or Server.serve_forever()
to make the server to start accepting connections.
""" """
raise NotImplementedError raise NotImplementedError
def create_datagram_endpoint(self, protocol_factory, async def connect_accepted_socket(
local_addr=None, remote_addr=None, *, self, protocol_factory, sock,
family=0, proto=0, flags=0, *, ssl=None,
reuse_address=None, reuse_port=None, ssl_handshake_timeout=None,
allow_broadcast=None, sock=None): ssl_shutdown_timeout=None):
"""Handle an accepted connection.
This is used by servers that accept connections outside of
asyncio, but use asyncio to handle connections.
This method is a coroutine. When completed, the coroutine
returns a (transport, protocol) pair.
"""
raise NotImplementedError
async def create_datagram_endpoint(self, protocol_factory,
local_addr=None, remote_addr=None, *,
family=0, proto=0, flags=0,
reuse_address=None, reuse_port=None,
allow_broadcast=None, sock=None):
"""A coroutine which creates a datagram endpoint. """A coroutine which creates a datagram endpoint.
This method will try to establish the endpoint in the background. This method will try to establish the endpoint in the background.
@@ -383,8 +464,8 @@ class AbstractEventLoop:
protocol_factory must be a callable returning a protocol instance. protocol_factory must be a callable returning a protocol instance.
socket family AF_INET or socket.AF_INET6 depending on host (or socket family AF_INET, socket.AF_INET6 or socket.AF_UNIX depending on
family if specified), socket type SOCK_DGRAM. host (or family if specified), socket type SOCK_DGRAM.
reuse_address tells the kernel to reuse a local socket in reuse_address tells the kernel to reuse a local socket in
TIME_WAIT state, without waiting for its natural timeout to TIME_WAIT state, without waiting for its natural timeout to
@@ -408,7 +489,7 @@ class AbstractEventLoop:
# Pipes and subprocesses. # Pipes and subprocesses.
def connect_read_pipe(self, protocol_factory, pipe): async def connect_read_pipe(self, protocol_factory, pipe):
"""Register read pipe in event loop. Set the pipe to non-blocking mode. """Register read pipe in event loop. Set the pipe to non-blocking mode.
protocol_factory should instantiate object with Protocol interface. protocol_factory should instantiate object with Protocol interface.
@@ -418,10 +499,10 @@ class AbstractEventLoop:
# The reason to accept file-like object instead of just file descriptor # The reason to accept file-like object instead of just file descriptor
# is: we need to own pipe and close it at transport finishing # is: we need to own pipe and close it at transport finishing
# Can got complicated errors if pass f.fileno(), # Can got complicated errors if pass f.fileno(),
# close fd in pipe transport then close f and vise versa. # close fd in pipe transport then close f and vice versa.
raise NotImplementedError raise NotImplementedError
def connect_write_pipe(self, protocol_factory, pipe): async def connect_write_pipe(self, protocol_factory, pipe):
"""Register write pipe in event loop. """Register write pipe in event loop.
protocol_factory should instantiate object with BaseProtocol interface. protocol_factory should instantiate object with BaseProtocol interface.
@@ -431,17 +512,21 @@ class AbstractEventLoop:
# The reason to accept file-like object instead of just file descriptor # The reason to accept file-like object instead of just file descriptor
# is: we need to own pipe and close it at transport finishing # is: we need to own pipe and close it at transport finishing
# Can got complicated errors if pass f.fileno(), # Can got complicated errors if pass f.fileno(),
# close fd in pipe transport then close f and vise versa. # close fd in pipe transport then close f and vice versa.
raise NotImplementedError raise NotImplementedError
def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE, async def subprocess_shell(self, protocol_factory, cmd, *,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,
**kwargs): stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs):
raise NotImplementedError raise NotImplementedError
def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE, async def subprocess_exec(self, protocol_factory, *args,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,
**kwargs): stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs):
raise NotImplementedError raise NotImplementedError
# Ready-based callback registration methods. # Ready-based callback registration methods.
@@ -463,16 +548,32 @@ class AbstractEventLoop:
# Completion based I/O methods returning Futures. # Completion based I/O methods returning Futures.
def sock_recv(self, sock, nbytes): async def sock_recv(self, sock, nbytes):
raise NotImplementedError raise NotImplementedError
def sock_sendall(self, sock, data): async def sock_recv_into(self, sock, buf):
raise NotImplementedError raise NotImplementedError
def sock_connect(self, sock, address): async def sock_recvfrom(self, sock, bufsize):
raise NotImplementedError raise NotImplementedError
def sock_accept(self, sock): async def sock_recvfrom_into(self, sock, buf, nbytes=0):
raise NotImplementedError
async def sock_sendall(self, sock, data):
raise NotImplementedError
async def sock_sendto(self, sock, data, address):
raise NotImplementedError
async def sock_connect(self, sock, address):
raise NotImplementedError
async def sock_accept(self, sock):
raise NotImplementedError
async def sock_sendfile(self, sock, file, offset=0, count=None,
*, fallback=None):
raise NotImplementedError raise NotImplementedError
# Signal handling. # Signal handling.
@@ -520,7 +621,7 @@ class AbstractEventLoopPolicy:
def get_event_loop(self): def get_event_loop(self):
"""Get the event loop for the current context. """Get the event loop for the current context.
Returns an event loop object implementing the BaseEventLoop interface, Returns an event loop object implementing the AbstractEventLoop interface,
or raises an exception in case no event loop has been set for the or raises an exception in case no event loop has been set for the
current context and the current policy does not specify to create one. current context and the current policy does not specify to create one.
@@ -571,23 +672,43 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
self._local = self._Local() self._local = self._Local()
def get_event_loop(self): def get_event_loop(self):
"""Get the event loop. """Get the event loop for the current context.
This may be None or an instance of EventLoop. Returns an instance of EventLoop or raises an exception.
""" """
if (self._local._loop is None and if (self._local._loop is None and
not self._local._set_called and not self._local._set_called and
isinstance(threading.current_thread(), threading._MainThread)): threading.current_thread() is threading.main_thread()):
stacklevel = 2
try:
f = sys._getframe(1)
except AttributeError:
pass
else:
# Move up the call stack so that the warning is attached
# to the line outside asyncio itself.
while f:
module = f.f_globals.get('__name__')
if not (module == 'asyncio' or module.startswith('asyncio.')):
break
f = f.f_back
stacklevel += 1
import warnings
warnings.warn('There is no current event loop',
DeprecationWarning, stacklevel=stacklevel)
self.set_event_loop(self.new_event_loop()) self.set_event_loop(self.new_event_loop())
if self._local._loop is None: if self._local._loop is None:
raise RuntimeError('There is no current event loop in thread %r.' raise RuntimeError('There is no current event loop in thread %r.'
% threading.current_thread().name) % threading.current_thread().name)
return self._local._loop return self._local._loop
def set_event_loop(self, loop): def set_event_loop(self, loop):
"""Set the event loop.""" """Set the event loop."""
self._local._set_called = True self._local._set_called = True
assert loop is None or isinstance(loop, AbstractEventLoop) if loop is not None and not isinstance(loop, AbstractEventLoop):
raise TypeError(f"loop must be an instance of AbstractEventLoop or None, not '{type(loop).__name__}'")
self._local._loop = loop self._local._loop = loop
def new_event_loop(self): def new_event_loop(self):
@@ -611,7 +732,9 @@ _lock = threading.Lock()
# A TLS for the running event loop, used by _get_running_loop. # A TLS for the running event loop, used by _get_running_loop.
class _RunningLoop(threading.local): class _RunningLoop(threading.local):
_loop = None loop_pid = (None, None)
_running_loop = _RunningLoop() _running_loop = _RunningLoop()
@@ -633,7 +756,10 @@ def _get_running_loop():
This is a low-level function intended to be used by event loops. This is a low-level function intended to be used by event loops.
This function is thread-specific. This function is thread-specific.
""" """
return _running_loop._loop # NOTE: this function is implemented in C (see _asynciomodule.c)
running_loop, pid = _running_loop.loop_pid
if running_loop is not None and pid == os.getpid():
return running_loop
def _set_running_loop(loop): def _set_running_loop(loop):
@@ -642,7 +768,8 @@ def _set_running_loop(loop):
This is a low-level function intended to be used by event loops. This is a low-level function intended to be used by event loops.
This function is thread-specific. This function is thread-specific.
""" """
_running_loop._loop = loop # NOTE: this function is implemented in C (see _asynciomodule.c)
_running_loop.loop_pid = (loop, os.getpid())
def _init_event_loop_policy(): def _init_event_loop_policy():
@@ -665,7 +792,8 @@ def set_event_loop_policy(policy):
If policy is None, the default policy is restored.""" If policy is None, the default policy is restored."""
global _event_loop_policy global _event_loop_policy
assert policy is None or isinstance(policy, AbstractEventLoopPolicy) if policy is not None and not isinstance(policy, AbstractEventLoopPolicy):
raise TypeError(f"policy must be an instance of AbstractEventLoopPolicy or None, not '{type(policy).__name__}'")
_event_loop_policy = policy _event_loop_policy = policy
@@ -678,6 +806,7 @@ def get_event_loop():
If there is no running event loop set, the function will return If there is no running event loop set, the function will return
the result of `get_event_loop_policy().get_event_loop()` call. the result of `get_event_loop_policy().get_event_loop()` call.
""" """
# NOTE: this function is implemented in C (see _asynciomodule.c)
current_loop = _get_running_loop() current_loop = _get_running_loop()
if current_loop is not None: if current_loop is not None:
return current_loop return current_loop
@@ -703,3 +832,37 @@ def set_child_watcher(watcher):
"""Equivalent to calling """Equivalent to calling
get_event_loop_policy().set_child_watcher(watcher).""" get_event_loop_policy().set_child_watcher(watcher)."""
return get_event_loop_policy().set_child_watcher(watcher) return get_event_loop_policy().set_child_watcher(watcher)
# Alias pure-Python implementations for testing purposes.
_py__get_running_loop = _get_running_loop
_py__set_running_loop = _set_running_loop
_py_get_running_loop = get_running_loop
_py_get_event_loop = get_event_loop
try:
# get_event_loop() is one of the most frequently called
# functions in asyncio. Pure Python implementation is
# about 4 times slower than C-accelerated.
from _asyncio import (_get_running_loop, _set_running_loop,
get_running_loop, get_event_loop)
except ImportError:
pass
else:
# Alias C implementations for testing purposes.
_c__get_running_loop = _get_running_loop
_c__set_running_loop = _set_running_loop
_c_get_running_loop = get_running_loop
_c_get_event_loop = get_event_loop
if hasattr(os, 'fork'):
def on_fork():
# Reset the loop and wakeupfd in the forked child process.
if _event_loop_policy is not None:
_event_loop_policy._local = BaseDefaultEventLoopPolicy._Local()
_set_running_loop(None)
signal.set_wakeup_fd(-1)
os.register_at_fork(after_in_child=on_fork)

62
Lib/asyncio/exceptions.py vendored Normal file
View File

@@ -0,0 +1,62 @@
"""asyncio exceptions."""
__all__ = ('BrokenBarrierError',
'CancelledError', 'InvalidStateError', 'TimeoutError',
'IncompleteReadError', 'LimitOverrunError',
'SendfileNotAvailableError')
class CancelledError(BaseException):
"""The Future or Task was cancelled."""
TimeoutError = TimeoutError # make local alias for the standard exception
class InvalidStateError(Exception):
"""The operation is not allowed in this state."""
class SendfileNotAvailableError(RuntimeError):
"""Sendfile syscall is not available.
Raised if OS does not support sendfile syscall for given socket or
file type.
"""
class IncompleteReadError(EOFError):
"""
Incomplete read error. Attributes:
- partial: read bytes string before the end of stream was reached
- expected: total number of expected bytes (or None if unknown)
"""
def __init__(self, partial, expected):
r_expected = 'undefined' if expected is None else repr(expected)
super().__init__(f'{len(partial)} bytes read on a total of '
f'{r_expected} expected bytes')
self.partial = partial
self.expected = expected
def __reduce__(self):
return type(self), (self.partial, self.expected)
class LimitOverrunError(Exception):
"""Reached the buffer limit while looking for a separator.
Attributes:
- consumed: total number of to be consumed bytes.
"""
def __init__(self, message, consumed):
super().__init__(message)
self.consumed = consumed
def __reduce__(self):
return type(self), (self.args[0], self.consumed)
class BrokenBarrierError(RuntimeError):
"""Barrier is broken by barrier.abort() call."""

76
Lib/asyncio/format_helpers.py vendored Normal file
View File

@@ -0,0 +1,76 @@
import functools
import inspect
import reprlib
import sys
import traceback
from . import constants
def _get_function_source(func):
func = inspect.unwrap(func)
if inspect.isfunction(func):
code = func.__code__
return (code.co_filename, code.co_firstlineno)
if isinstance(func, functools.partial):
return _get_function_source(func.func)
if isinstance(func, functools.partialmethod):
return _get_function_source(func.func)
return None
def _format_callback_source(func, args):
func_repr = _format_callback(func, args, None)
source = _get_function_source(func)
if source:
func_repr += f' at {source[0]}:{source[1]}'
return func_repr
def _format_args_and_kwargs(args, kwargs):
"""Format function arguments and keyword arguments.
Special case for a single parameter: ('hello',) is formatted as ('hello').
"""
# use reprlib to limit the length of the output
items = []
if args:
items.extend(reprlib.repr(arg) for arg in args)
if kwargs:
items.extend(f'{k}={reprlib.repr(v)}' for k, v in kwargs.items())
return '({})'.format(', '.join(items))
def _format_callback(func, args, kwargs, suffix=''):
if isinstance(func, functools.partial):
suffix = _format_args_and_kwargs(args, kwargs) + suffix
return _format_callback(func.func, func.args, func.keywords, suffix)
if hasattr(func, '__qualname__') and func.__qualname__:
func_repr = func.__qualname__
elif hasattr(func, '__name__') and func.__name__:
func_repr = func.__name__
else:
func_repr = repr(func)
func_repr += _format_args_and_kwargs(args, kwargs)
if suffix:
func_repr += suffix
return func_repr
def extract_stack(f=None, limit=None):
"""Replacement for traceback.extract_stack() that only does the
necessary work for asyncio debug mode.
"""
if f is None:
f = sys._getframe().f_back
if limit is None:
# Limit the amount of work to a reasonable amount, as extract_stack()
# can be called for each coroutine and future in debug mode.
limit = constants.DEBUG_STACK_DEPTH
stack = traceback.StackSummary.extract(traceback.walk_stack(f),
limit=limit,
lookup_lines=False)
stack.reverse()
return stack

298
Lib/asyncio/futures.py vendored
View File

@@ -1,21 +1,21 @@
"""A Future class similar to the one in PEP 3148.""" """A Future class similar to the one in PEP 3148."""
__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError', __all__ = (
'Future', 'wrap_future', 'isfuture'] 'Future', 'wrap_future', 'isfuture',
)
import concurrent.futures import concurrent.futures
import contextvars
import logging import logging
import sys import sys
import traceback from types import GenericAlias
from . import base_futures from . import base_futures
from . import compat
from . import events from . import events
from . import exceptions
from . import format_helpers
CancelledError = base_futures.CancelledError
InvalidStateError = base_futures.InvalidStateError
TimeoutError = base_futures.TimeoutError
isfuture = base_futures.isfuture isfuture = base_futures.isfuture
@@ -27,96 +27,18 @@ _FINISHED = base_futures._FINISHED
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
class _TracebackLogger:
"""Helper to log a traceback upon destruction if not cleared.
This solves a nasty problem with Futures and Tasks that have an
exception set: if nobody asks for the exception, the exception is
never logged. This violates the Zen of Python: 'Errors should
never pass silently. Unless explicitly silenced.'
However, we don't want to log the exception as soon as
set_exception() is called: if the calling code is written
properly, it will get the exception and handle it properly. But
we *do* want to log it if result() or exception() was never called
-- otherwise developers waste a lot of time wondering why their
buggy code fails silently.
An earlier attempt added a __del__() method to the Future class
itself, but this backfired because the presence of __del__()
prevents garbage collection from breaking cycles. A way out of
this catch-22 is to avoid having a __del__() method on the Future
class itself, but instead to have a reference to a helper object
with a __del__() method that logs the traceback, where we ensure
that the helper object doesn't participate in cycles, and only the
Future has a reference to it.
The helper object is added when set_exception() is called. When
the Future is collected, and the helper is present, the helper
object is also collected, and its __del__() method will log the
traceback. When the Future's result() or exception() method is
called (and a helper object is present), it removes the helper
object, after calling its clear() method to prevent it from
logging.
One downside is that we do a fair amount of work to extract the
traceback from the exception, even when it is never logged. It
would seem cheaper to just store the exception object, but that
references the traceback, which references stack frames, which may
reference the Future, which references the _TracebackLogger, and
then the _TracebackLogger would be included in a cycle, which is
what we're trying to avoid! As an optimization, we don't
immediately format the exception; we only do the work when
activate() is called, which call is delayed until after all the
Future's callbacks have run. Since usually a Future has at least
one callback (typically set by 'yield from') and usually that
callback extracts the callback, thereby removing the need to
format the exception.
PS. I don't claim credit for this solution. I first heard of it
in a discussion about closing files when they are collected.
"""
__slots__ = ('loop', 'source_traceback', 'exc', 'tb')
def __init__(self, future, exc):
self.loop = future._loop
self.source_traceback = future._source_traceback
self.exc = exc
self.tb = None
def activate(self):
exc = self.exc
if exc is not None:
self.exc = None
self.tb = traceback.format_exception(exc.__class__, exc,
exc.__traceback__)
def clear(self):
self.exc = None
self.tb = None
def __del__(self):
if self.tb:
msg = 'Future/Task exception was never retrieved\n'
if self.source_traceback:
src = ''.join(traceback.format_list(self.source_traceback))
msg += 'Future/Task created at (most recent call last):\n'
msg += '%s\n' % src.rstrip()
msg += ''.join(self.tb).rstrip()
self.loop.call_exception_handler({'message': msg})
class Future: class Future:
"""This class is *almost* compatible with concurrent.futures.Future. """This class is *almost* compatible with concurrent.futures.Future.
Differences: Differences:
- This class is not thread-safe.
- result() and exception() do not take a timeout argument and - result() and exception() do not take a timeout argument and
raise an exception when the future isn't done yet. raise an exception when the future isn't done yet.
- Callbacks registered with add_done_callback() are always called - Callbacks registered with add_done_callback() are always called
via the event loop's call_soon_threadsafe(). via the event loop's call_soon().
- This class is not compatible with the wait() and as_completed() - This class is not compatible with the wait() and as_completed()
methods in the concurrent.futures package. methods in the concurrent.futures package.
@@ -130,6 +52,9 @@ class Future:
_exception = None _exception = None
_loop = None _loop = None
_source_traceback = None _source_traceback = None
_cancel_message = None
# A saved CancelledError for later chaining as an exception context.
_cancelled_exc = None
# This field is used for a dual purpose: # This field is used for a dual purpose:
# - Its presence is a marker to declare that a class implements # - Its presence is a marker to declare that a class implements
@@ -137,12 +62,12 @@ class Future:
# The value must also be not-None, to enable a subclass to declare # The value must also be not-None, to enable a subclass to declare
# that it is not compatible by setting this to None. # that it is not compatible by setting this to None.
# - It is set by __iter__() below so that Task._step() can tell # - It is set by __iter__() below so that Task._step() can tell
# the difference between `yield from Future()` (correct) vs. # the difference between
# `await Future()` or`yield from Future()` (correct) vs.
# `yield Future()` (incorrect). # `yield Future()` (incorrect).
_asyncio_future_blocking = False _asyncio_future_blocking = False
_log_traceback = False # Used for Python 3.4 and later __log_traceback = False
_tb_logger = None # Used for Python 3.3 only
def __init__(self, *, loop=None): def __init__(self, *, loop=None):
"""Initialize the future. """Initialize the future.
@@ -157,50 +82,83 @@ class Future:
self._loop = loop self._loop = loop
self._callbacks = [] self._callbacks = []
if self._loop.get_debug(): if self._loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1)) self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
_repr_info = base_futures._future_repr_info
def __repr__(self): def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info())) return base_futures._future_repr(self)
# On Python 3.3 and older, objects with a destructor part of a reference def __del__(self):
# cycle are never destroyed. It's not more the case on Python 3.4 thanks if not self.__log_traceback:
# to the PEP 442. # set_exception() was not called, or result() or exception()
if compat.PY34: # has consumed the exception
def __del__(self): return
if not self._log_traceback: exc = self._exception
# set_exception() was not called, or result() or exception() context = {
# has consumed the exception 'message':
return f'{self.__class__.__name__} exception was never retrieved',
exc = self._exception 'exception': exc,
context = { 'future': self,
'message': ('%s exception was never retrieved' }
% self.__class__.__name__), if self._source_traceback:
'exception': exc, context['source_traceback'] = self._source_traceback
'future': self, self._loop.call_exception_handler(context)
}
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)
def __class_getitem__(cls, type): __class_getitem__ = classmethod(GenericAlias)
return cls
def cancel(self): @property
def _log_traceback(self):
return self.__log_traceback
@_log_traceback.setter
def _log_traceback(self, val):
if val:
raise ValueError('_log_traceback can only be set to False')
self.__log_traceback = False
def get_loop(self):
"""Return the event loop the Future is bound to."""
loop = self._loop
if loop is None:
raise RuntimeError("Future object is not initialized.")
return loop
def _make_cancelled_error(self):
"""Create the CancelledError to raise if the Future is cancelled.
This should only be called once when handling a cancellation since
it erases the saved context exception value.
"""
if self._cancelled_exc is not None:
exc = self._cancelled_exc
self._cancelled_exc = None
return exc
if self._cancel_message is None:
exc = exceptions.CancelledError()
else:
exc = exceptions.CancelledError(self._cancel_message)
exc.__context__ = self._cancelled_exc
# Remove the reference since we don't need this anymore.
self._cancelled_exc = None
return exc
def cancel(self, msg=None):
"""Cancel the future and schedule callbacks. """Cancel the future and schedule callbacks.
If the future is already done or cancelled, return False. Otherwise, If the future is already done or cancelled, return False. Otherwise,
change the future's state to cancelled, schedule the callbacks and change the future's state to cancelled, schedule the callbacks and
return True. return True.
""" """
self.__log_traceback = False
if self._state != _PENDING: if self._state != _PENDING:
return False return False
self._state = _CANCELLED self._state = _CANCELLED
self._schedule_callbacks() self._cancel_message = msg
self.__schedule_callbacks()
return True return True
def _schedule_callbacks(self): def __schedule_callbacks(self):
"""Internal: Ask the event loop to call all callbacks. """Internal: Ask the event loop to call all callbacks.
The callbacks are scheduled to be called as soon as possible. Also The callbacks are scheduled to be called as soon as possible. Also
@@ -211,8 +169,8 @@ class Future:
return return
self._callbacks[:] = [] self._callbacks[:] = []
for callback in callbacks: for callback, ctx in callbacks:
self._loop.call_soon(callback, self) self._loop.call_soon(callback, self, context=ctx)
def cancelled(self): def cancelled(self):
"""Return True if the future was cancelled.""" """Return True if the future was cancelled."""
@@ -236,15 +194,13 @@ class Future:
the future is done and has an exception set, this exception is raised. the future is done and has an exception set, this exception is raised.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise CancelledError exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED: if self._state != _FINISHED:
raise InvalidStateError('Result is not ready.') raise exceptions.InvalidStateError('Result is not ready.')
self._log_traceback = False self.__log_traceback = False
if self._tb_logger is not None:
self._tb_logger.clear()
self._tb_logger = None
if self._exception is not None: if self._exception is not None:
raise self._exception raise self._exception.with_traceback(self._exception_tb)
return self._result return self._result
def exception(self): def exception(self):
@@ -256,16 +212,14 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise CancelledError exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED: if self._state != _FINISHED:
raise InvalidStateError('Exception is not set.') raise exceptions.InvalidStateError('Exception is not set.')
self._log_traceback = False self.__log_traceback = False
if self._tb_logger is not None:
self._tb_logger.clear()
self._tb_logger = None
return self._exception return self._exception
def add_done_callback(self, fn): def add_done_callback(self, fn, *, context=None):
"""Add a callback to be run when the future becomes done. """Add a callback to be run when the future becomes done.
The callback is called with a single argument - the future object. If The callback is called with a single argument - the future object. If
@@ -273,9 +227,11 @@ class Future:
scheduled with call_soon. scheduled with call_soon.
""" """
if self._state != _PENDING: if self._state != _PENDING:
self._loop.call_soon(fn, self) self._loop.call_soon(fn, self, context=context)
else: else:
self._callbacks.append(fn) if context is None:
context = contextvars.copy_context()
self._callbacks.append((fn, context))
# New method not in PEP 3148. # New method not in PEP 3148.
@@ -284,7 +240,9 @@ class Future:
Returns the number of callbacks removed. Returns the number of callbacks removed.
""" """
filtered_callbacks = [f for f in self._callbacks if f != fn] filtered_callbacks = [(f, ctx)
for (f, ctx) in self._callbacks
if f != fn]
removed_count = len(self._callbacks) - len(filtered_callbacks) removed_count = len(self._callbacks) - len(filtered_callbacks)
if removed_count: if removed_count:
self._callbacks[:] = filtered_callbacks self._callbacks[:] = filtered_callbacks
@@ -299,10 +257,10 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state != _PENDING: if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self)) raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
self._result = result self._result = result
self._state = _FINISHED self._state = _FINISHED
self._schedule_callbacks() self.__schedule_callbacks()
def set_exception(self, exception): def set_exception(self, exception):
"""Mark the future done and set an exception. """Mark the future done and set an exception.
@@ -311,38 +269,45 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state != _PENDING: if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self)) raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
if isinstance(exception, type): if isinstance(exception, type):
exception = exception() exception = exception()
if type(exception) is StopIteration: if type(exception) is StopIteration:
raise TypeError("StopIteration interacts badly with generators " raise TypeError("StopIteration interacts badly with generators "
"and cannot be raised into a Future") "and cannot be raised into a Future")
self._exception = exception self._exception = exception
self._exception_tb = exception.__traceback__
self._state = _FINISHED self._state = _FINISHED
self._schedule_callbacks() self.__schedule_callbacks()
if compat.PY34: self.__log_traceback = True
self._log_traceback = True
else:
self._tb_logger = _TracebackLogger(self, exception)
# Arrange for the logger to be activated after all callbacks
# have had a chance to call result() or exception().
self._loop.call_soon(self._tb_logger.activate)
def __iter__(self): def __await__(self):
if not self.done(): if not self.done():
self._asyncio_future_blocking = True self._asyncio_future_blocking = True
yield self # This tells Task to wait for completion. yield self # This tells Task to wait for completion.
assert self.done(), "yield from wasn't used with future" if not self.done():
raise RuntimeError("await wasn't used with future")
return self.result() # May raise too. return self.result() # May raise too.
if compat.PY35: __iter__ = __await__ # make compatible with 'yield from'.
__await__ = __iter__ # make compatible with 'await' expression
# Needed for testing purposes. # Needed for testing purposes.
_PyFuture = Future _PyFuture = Future
def _get_loop(fut):
# Tries to call Future.get_loop() if it's available.
# Otherwise fallbacks to using the old '_loop' property.
try:
get_loop = fut.get_loop
except AttributeError:
pass
else:
return get_loop()
return fut._loop
def _set_result_unless_cancelled(fut, result): def _set_result_unless_cancelled(fut, result):
"""Helper setting the result only if the future was not cancelled.""" """Helper setting the result only if the future was not cancelled."""
if fut.cancelled(): if fut.cancelled():
@@ -350,6 +315,18 @@ def _set_result_unless_cancelled(fut, result):
fut.set_result(result) fut.set_result(result)
def _convert_future_exc(exc):
exc_class = type(exc)
if exc_class is concurrent.futures.CancelledError:
return exceptions.CancelledError(*exc.args)
elif exc_class is concurrent.futures.TimeoutError:
return exceptions.TimeoutError(*exc.args)
elif exc_class is concurrent.futures.InvalidStateError:
return exceptions.InvalidStateError(*exc.args)
else:
return exc
def _set_concurrent_future_state(concurrent, source): def _set_concurrent_future_state(concurrent, source):
"""Copy state from a future to a concurrent.futures.Future.""" """Copy state from a future to a concurrent.futures.Future."""
assert source.done() assert source.done()
@@ -359,7 +336,7 @@ def _set_concurrent_future_state(concurrent, source):
return return
exception = source.exception() exception = source.exception()
if exception is not None: if exception is not None:
concurrent.set_exception(exception) concurrent.set_exception(_convert_future_exc(exception))
else: else:
result = source.result() result = source.result()
concurrent.set_result(result) concurrent.set_result(result)
@@ -379,7 +356,7 @@ def _copy_future_state(source, dest):
else: else:
exception = source.exception() exception = source.exception()
if exception is not None: if exception is not None:
dest.set_exception(exception) dest.set_exception(_convert_future_exc(exception))
else: else:
result = source.result() result = source.result()
dest.set_result(result) dest.set_result(result)
@@ -398,8 +375,8 @@ def _chain_future(source, destination):
if not isfuture(destination) and not isinstance(destination, if not isfuture(destination) and not isinstance(destination,
concurrent.futures.Future): concurrent.futures.Future):
raise TypeError('A future is required for destination argument') raise TypeError('A future is required for destination argument')
source_loop = source._loop if isfuture(source) else None source_loop = _get_loop(source) if isfuture(source) else None
dest_loop = destination._loop if isfuture(destination) else None dest_loop = _get_loop(destination) if isfuture(destination) else None
def _set_state(future, other): def _set_state(future, other):
if isfuture(future): if isfuture(future):
@@ -415,9 +392,14 @@ def _chain_future(source, destination):
source_loop.call_soon_threadsafe(source.cancel) source_loop.call_soon_threadsafe(source.cancel)
def _call_set_state(source): def _call_set_state(source):
if (destination.cancelled() and
dest_loop is not None and dest_loop.is_closed()):
return
if dest_loop is None or dest_loop is source_loop: if dest_loop is None or dest_loop is source_loop:
_set_state(destination, source) _set_state(destination, source)
else: else:
if dest_loop.is_closed():
return
dest_loop.call_soon_threadsafe(_set_state, destination, source) dest_loop.call_soon_threadsafe(_set_state, destination, source)
destination.add_done_callback(_call_check_cancel) destination.add_done_callback(_call_check_cancel)
@@ -429,7 +411,7 @@ def wrap_future(future, *, loop=None):
if isfuture(future): if isfuture(future):
return future return future
assert isinstance(future, concurrent.futures.Future), \ assert isinstance(future, concurrent.futures.Future), \
'concurrent.futures.Future is expected, got {!r}'.format(future) f'concurrent.futures.Future is expected, got {future!r}'
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
new_future = loop.create_future() new_future = loop.create_future()

456
Lib/asyncio/locks.py vendored
View File

@@ -1,92 +1,26 @@
"""Synchronization primitives.""" """Synchronization primitives."""
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore'] __all__ = ('Lock', 'Event', 'Condition', 'Semaphore',
'BoundedSemaphore', 'Barrier')
import collections import collections
import enum
from . import compat from . import exceptions
from . import events from . import mixins
from . import futures
from .coroutines import coroutine
class _ContextManagerMixin:
class _ContextManager: async def __aenter__(self):
"""Context manager. await self.acquire()
This enables the following idiom for acquiring and releasing a
lock around a block:
with (yield from lock):
<block>
while failing loudly when accidentally using:
with lock:
<block>
"""
def __init__(self, lock):
self._lock = lock
def __enter__(self):
# We have no use for the "as ..." clause in the with # We have no use for the "as ..." clause in the with
# statement for locks. # statement for locks.
return None return None
def __exit__(self, *args): async def __aexit__(self, exc_type, exc, tb):
try: self.release()
self._lock.release()
finally:
self._lock = None # Crudely prevent reuse.
class _ContextManagerMixin: class Lock(_ContextManagerMixin, mixins._LoopBoundMixin):
def __enter__(self):
raise RuntimeError(
'"yield from" should be used as context manager expression')
def __exit__(self, *args):
# This must exist because __enter__ exists, even though that
# always raises; that's how the with-statement works.
pass
@coroutine
def __iter__(self):
# This is not a coroutine. It is meant to enable the idiom:
#
# with (yield from lock):
# <block>
#
# as an alternative to:
#
# yield from lock.acquire()
# try:
# <block>
# finally:
# lock.release()
yield from self.acquire()
return _ContextManager(self)
if compat.PY35:
def __await__(self):
# To make "with await lock" work.
yield from self.acquire()
return _ContextManager(self)
@coroutine
def __aenter__(self):
yield from self.acquire()
# We have no use for the "as ..." clause in the with
# statement for locks.
return None
@coroutine
def __aexit__(self, exc_type, exc, tb):
self.release()
class Lock(_ContextManagerMixin):
"""Primitive lock objects. """Primitive lock objects.
A primitive lock is a synchronization primitive that is not owned A primitive lock is a synchronization primitive that is not owned
@@ -108,16 +42,16 @@ class Lock(_ContextManagerMixin):
release() call resets the state to unlocked; first coroutine which release() call resets the state to unlocked; first coroutine which
is blocked in acquire() is being processed. is blocked in acquire() is being processed.
acquire() is a coroutine and should be called with 'yield from'. acquire() is a coroutine and should be called with 'await'.
Locks also support the context management protocol. '(yield from lock)' Locks also support the asynchronous context management protocol.
should be used as the context manager expression. 'async with lock' statement should be used.
Usage: Usage:
lock = Lock() lock = Lock()
... ...
yield from lock await lock.acquire()
try: try:
... ...
finally: finally:
@@ -127,57 +61,65 @@ class Lock(_ContextManagerMixin):
lock = Lock() lock = Lock()
... ...
with (yield from lock): async with lock:
... ...
Lock objects can be tested for locking state: Lock objects can be tested for locking state:
if not lock.locked(): if not lock.locked():
yield from lock await lock.acquire()
else: else:
# lock is acquired # lock is acquired
... ...
""" """
def __init__(self, *, loop=None): def __init__(self):
self._waiters = collections.deque() self._waiters = None
self._locked = False self._locked = False
if loop is not None:
self._loop = loop
else:
self._loop = events.get_event_loop()
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
extra = 'locked' if self._locked else 'unlocked' extra = 'locked' if self._locked else 'unlocked'
if self._waiters: if self._waiters:
extra = '{},waiters:{}'.format(extra, len(self._waiters)) extra = f'{extra}, waiters:{len(self._waiters)}'
return '<{} [{}]>'.format(res[1:-1], extra) return f'<{res[1:-1]} [{extra}]>'
def locked(self): def locked(self):
"""Return True if lock is acquired.""" """Return True if lock is acquired."""
return self._locked return self._locked
@coroutine async def acquire(self):
def acquire(self):
"""Acquire a lock. """Acquire a lock.
This method blocks until the lock is unlocked, then sets it to This method blocks until the lock is unlocked, then sets it to
locked and returns True. locked and returns True.
""" """
if not self._locked and all(w.cancelled() for w in self._waiters): if (not self._locked and (self._waiters is None or
all(w.cancelled() for w in self._waiters))):
self._locked = True self._locked = True
return True return True
fut = self._loop.create_future() if self._waiters is None:
self._waiters = collections.deque()
fut = self._get_loop().create_future()
self._waiters.append(fut) self._waiters.append(fut)
# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try: try:
yield from fut try:
self._locked = True await fut
return True finally:
finally: self._waiters.remove(fut)
self._waiters.remove(fut) except exceptions.CancelledError:
if not self._locked:
self._wake_up_first()
raise
self._locked = True
return True
def release(self): def release(self):
"""Release a lock. """Release a lock.
@@ -192,16 +134,27 @@ class Lock(_ContextManagerMixin):
""" """
if self._locked: if self._locked:
self._locked = False self._locked = False
# Wake up the first waiter who isn't cancelled. self._wake_up_first()
for fut in self._waiters:
if not fut.done():
fut.set_result(True)
break
else: else:
raise RuntimeError('Lock is not acquired.') raise RuntimeError('Lock is not acquired.')
def _wake_up_first(self):
"""Wake up the first waiter if it isn't done."""
if not self._waiters:
return
try:
fut = next(iter(self._waiters))
except StopIteration:
return
class Event: # .done() necessarily means that a waiter will wake up later on and
# either take the lock, or, if it was cancelled and lock wasn't
# taken already, will hit this again and wake up a new waiter.
if not fut.done():
fut.set_result(True)
class Event(mixins._LoopBoundMixin):
"""Asynchronous equivalent to threading.Event. """Asynchronous equivalent to threading.Event.
Class implementing event objects. An event manages a flag that can be set Class implementing event objects. An event manages a flag that can be set
@@ -210,20 +163,16 @@ class Event:
false. false.
""" """
def __init__(self, *, loop=None): def __init__(self):
self._waiters = collections.deque() self._waiters = collections.deque()
self._value = False self._value = False
if loop is not None:
self._loop = loop
else:
self._loop = events.get_event_loop()
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
extra = 'set' if self._value else 'unset' extra = 'set' if self._value else 'unset'
if self._waiters: if self._waiters:
extra = '{},waiters:{}'.format(extra, len(self._waiters)) extra = f'{extra}, waiters:{len(self._waiters)}'
return '<{} [{}]>'.format(res[1:-1], extra) return f'<{res[1:-1]} [{extra}]>'
def is_set(self): def is_set(self):
"""Return True if and only if the internal flag is true.""" """Return True if and only if the internal flag is true."""
@@ -247,8 +196,7 @@ class Event:
to true again.""" to true again."""
self._value = False self._value = False
@coroutine async def wait(self):
def wait(self):
"""Block until the internal flag is true. """Block until the internal flag is true.
If the internal flag is true on entry, return True If the internal flag is true on entry, return True
@@ -258,16 +206,16 @@ class Event:
if self._value: if self._value:
return True return True
fut = self._loop.create_future() fut = self._get_loop().create_future()
self._waiters.append(fut) self._waiters.append(fut)
try: try:
yield from fut await fut
return True return True
finally: finally:
self._waiters.remove(fut) self._waiters.remove(fut)
class Condition(_ContextManagerMixin): class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
"""Asynchronous equivalent to threading.Condition. """Asynchronous equivalent to threading.Condition.
This class implements condition variable objects. A condition variable This class implements condition variable objects. A condition variable
@@ -277,16 +225,9 @@ class Condition(_ContextManagerMixin):
A new Lock object is created and used as the underlying lock. A new Lock object is created and used as the underlying lock.
""" """
def __init__(self, lock=None, *, loop=None): def __init__(self, lock=None):
if loop is not None:
self._loop = loop
else:
self._loop = events.get_event_loop()
if lock is None: if lock is None:
lock = Lock(loop=self._loop) lock = Lock()
elif lock._loop is not self._loop:
raise ValueError("loop argument must agree with lock")
self._lock = lock self._lock = lock
# Export the lock's locked(), acquire() and release() methods. # Export the lock's locked(), acquire() and release() methods.
@@ -300,11 +241,10 @@ class Condition(_ContextManagerMixin):
res = super().__repr__() res = super().__repr__()
extra = 'locked' if self.locked() else 'unlocked' extra = 'locked' if self.locked() else 'unlocked'
if self._waiters: if self._waiters:
extra = '{},waiters:{}'.format(extra, len(self._waiters)) extra = f'{extra}, waiters:{len(self._waiters)}'
return '<{} [{}]>'.format(res[1:-1], extra) return f'<{res[1:-1]} [{extra}]>'
@coroutine async def wait(self):
def wait(self):
"""Wait until notified. """Wait until notified.
If the calling coroutine has not acquired the lock when this If the calling coroutine has not acquired the lock when this
@@ -320,25 +260,28 @@ class Condition(_ContextManagerMixin):
self.release() self.release()
try: try:
fut = self._loop.create_future() fut = self._get_loop().create_future()
self._waiters.append(fut) self._waiters.append(fut)
try: try:
yield from fut await fut
return True return True
finally: finally:
self._waiters.remove(fut) self._waiters.remove(fut)
finally: finally:
# Must reacquire lock even if wait is cancelled # Must reacquire lock even if wait is cancelled
cancelled = False
while True: while True:
try: try:
yield from self.acquire() await self.acquire()
break break
except futures.CancelledError: except exceptions.CancelledError:
pass cancelled = True
@coroutine if cancelled:
def wait_for(self, predicate): raise exceptions.CancelledError
async def wait_for(self, predicate):
"""Wait until a predicate becomes true. """Wait until a predicate becomes true.
The predicate should be a callable which result will be The predicate should be a callable which result will be
@@ -347,7 +290,7 @@ class Condition(_ContextManagerMixin):
""" """
result = predicate() result = predicate()
while not result: while not result:
yield from self.wait() await self.wait()
result = predicate() result = predicate()
return result return result
@@ -384,7 +327,7 @@ class Condition(_ContextManagerMixin):
self.notify(len(self._waiters)) self.notify(len(self._waiters))
class Semaphore(_ContextManagerMixin): class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):
"""A Semaphore implementation. """A Semaphore implementation.
A semaphore manages an internal counter which is decremented by each A semaphore manages an internal counter which is decremented by each
@@ -399,37 +342,25 @@ class Semaphore(_ContextManagerMixin):
ValueError is raised. ValueError is raised.
""" """
def __init__(self, value=1, *, loop=None): def __init__(self, value=1):
if value < 0: if value < 0:
raise ValueError("Semaphore initial value must be >= 0") raise ValueError("Semaphore initial value must be >= 0")
self._waiters = None
self._value = value self._value = value
self._waiters = collections.deque()
if loop is not None:
self._loop = loop
else:
self._loop = events.get_event_loop()
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
extra = 'locked' if self.locked() else 'unlocked,value:{}'.format( extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'
self._value)
if self._waiters: if self._waiters:
extra = '{},waiters:{}'.format(extra, len(self._waiters)) extra = f'{extra}, waiters:{len(self._waiters)}'
return '<{} [{}]>'.format(res[1:-1], extra) return f'<{res[1:-1]} [{extra}]>'
def _wake_up_next(self):
while self._waiters:
waiter = self._waiters.popleft()
if not waiter.done():
waiter.set_result(None)
return
def locked(self): def locked(self):
"""Returns True if semaphore can not be acquired immediately.""" """Returns True if semaphore cannot be acquired immediately."""
return self._value == 0 return self._value == 0 or (
any(not w.cancelled() for w in (self._waiters or ())))
@coroutine async def acquire(self):
def acquire(self):
"""Acquire a semaphore. """Acquire a semaphore.
If the internal counter is larger than zero on entry, If the internal counter is larger than zero on entry,
@@ -438,28 +369,53 @@ class Semaphore(_ContextManagerMixin):
called release() to make it larger than 0, and then return called release() to make it larger than 0, and then return
True. True.
""" """
while self._value <= 0: if not self.locked():
fut = self._loop.create_future() self._value -= 1
self._waiters.append(fut) return True
if self._waiters is None:
self._waiters = collections.deque()
fut = self._get_loop().create_future()
self._waiters.append(fut)
# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try:
try: try:
yield from fut await fut
except: finally:
# See the similar code in Queue.get. self._waiters.remove(fut)
fut.cancel() except exceptions.CancelledError:
if self._value > 0 and not fut.cancelled(): if not fut.cancelled():
self._wake_up_next() self._value += 1
raise self._wake_up_next()
self._value -= 1 raise
if self._value > 0:
self._wake_up_next()
return True return True
def release(self): def release(self):
"""Release a semaphore, incrementing the internal counter by one. """Release a semaphore, incrementing the internal counter by one.
When it was zero on entry and another coroutine is waiting for it to When it was zero on entry and another coroutine is waiting for it to
become larger than zero again, wake up that coroutine. become larger than zero again, wake up that coroutine.
""" """
self._value += 1 self._value += 1
self._wake_up_next() self._wake_up_next()
def _wake_up_next(self):
"""Wake up the first waiter that isn't done."""
if not self._waiters:
return
for fut in self._waiters:
if not fut.done():
self._value -= 1
fut.set_result(True)
return
class BoundedSemaphore(Semaphore): class BoundedSemaphore(Semaphore):
"""A bounded semaphore implementation. """A bounded semaphore implementation.
@@ -468,11 +424,163 @@ class BoundedSemaphore(Semaphore):
above the initial value. above the initial value.
""" """
def __init__(self, value=1, *, loop=None): def __init__(self, value=1):
self._bound_value = value self._bound_value = value
super().__init__(value, loop=loop) super().__init__(value)
def release(self): def release(self):
if self._value >= self._bound_value: if self._value >= self._bound_value:
raise ValueError('BoundedSemaphore released too many times') raise ValueError('BoundedSemaphore released too many times')
super().release() super().release()
class _BarrierState(enum.Enum):
FILLING = 'filling'
DRAINING = 'draining'
RESETTING = 'resetting'
BROKEN = 'broken'
class Barrier(mixins._LoopBoundMixin):
"""Asyncio equivalent to threading.Barrier
Implements a Barrier primitive.
Useful for synchronizing a fixed number of tasks at known synchronization
points. Tasks block on 'wait()' and are simultaneously awoken once they
have all made their call.
"""
def __init__(self, parties):
"""Create a barrier, initialised to 'parties' tasks."""
if parties < 1:
raise ValueError('parties must be > 0')
self._cond = Condition() # notify all tasks when state changes
self._parties = parties
self._state = _BarrierState.FILLING
self._count = 0 # count tasks in Barrier
def __repr__(self):
res = super().__repr__()
extra = f'{self._state.value}'
if not self.broken:
extra += f', waiters:{self.n_waiting}/{self.parties}'
return f'<{res[1:-1]} [{extra}]>'
async def __aenter__(self):
# wait for the barrier reaches the parties number
# when start draining release and return index of waited task
return await self.wait()
async def __aexit__(self, *args):
pass
async def wait(self):
"""Wait for the barrier.
When the specified number of tasks have started waiting, they are all
simultaneously awoken.
Returns an unique and individual index number from 0 to 'parties-1'.
"""
async with self._cond:
await self._block() # Block while the barrier drains or resets.
try:
index = self._count
self._count += 1
if index + 1 == self._parties:
# We release the barrier
await self._release()
else:
await self._wait()
return index
finally:
self._count -= 1
# Wake up any tasks waiting for barrier to drain.
self._exit()
async def _block(self):
# Block until the barrier is ready for us,
# or raise an exception if it is broken.
#
# It is draining or resetting, wait until done
# unless a CancelledError occurs
await self._cond.wait_for(
lambda: self._state not in (
_BarrierState.DRAINING, _BarrierState.RESETTING
)
)
# see if the barrier is in a broken state
if self._state is _BarrierState.BROKEN:
raise exceptions.BrokenBarrierError("Barrier aborted")
async def _release(self):
# Release the tasks waiting in the barrier.
# Enter draining state.
# Next waiting tasks will be blocked until the end of draining.
self._state = _BarrierState.DRAINING
self._cond.notify_all()
async def _wait(self):
# Wait in the barrier until we are released. Raise an exception
# if the barrier is reset or broken.
# wait for end of filling
# unless a CancelledError occurs
await self._cond.wait_for(lambda: self._state is not _BarrierState.FILLING)
if self._state in (_BarrierState.BROKEN, _BarrierState.RESETTING):
raise exceptions.BrokenBarrierError("Abort or reset of barrier")
def _exit(self):
# If we are the last tasks to exit the barrier, signal any tasks
# waiting for the barrier to drain.
if self._count == 0:
if self._state in (_BarrierState.RESETTING, _BarrierState.DRAINING):
self._state = _BarrierState.FILLING
self._cond.notify_all()
async def reset(self):
"""Reset the barrier to the initial state.
Any tasks currently waiting will get the BrokenBarrier exception
raised.
"""
async with self._cond:
if self._count > 0:
if self._state is not _BarrierState.RESETTING:
#reset the barrier, waking up tasks
self._state = _BarrierState.RESETTING
else:
self._state = _BarrierState.FILLING
self._cond.notify_all()
async def abort(self):
"""Place the barrier into a 'broken' state.
Useful in case of error. Any currently waiting tasks and tasks
attempting to 'wait()' will have BrokenBarrierError raised.
"""
async with self._cond:
self._state = _BarrierState.BROKEN
self._cond.notify_all()
@property
def parties(self):
"""Return the number of tasks required to trip the barrier."""
return self._parties
@property
def n_waiting(self):
"""Return the number of tasks currently waiting at the barrier."""
if self._state is _BarrierState.FILLING:
return self._count
return 0
@property
def broken(self):
"""Return True if the barrier is in a broken state."""
return self._state is _BarrierState.BROKEN

21
Lib/asyncio/mixins.py vendored Normal file
View File

@@ -0,0 +1,21 @@
"""Event loop mixins."""
import threading
from . import events
_global_lock = threading.Lock()
class _LoopBoundMixin:
_loop = None
def _get_loop(self):
loop = events._get_running_loop()
if self._loop is None:
with _global_lock:
if self._loop is None:
self._loop = loop
if loop is not self._loop:
raise RuntimeError(f'{self!r} is bound to a different event loop')
return loop

View File

@@ -4,20 +4,45 @@ A proactor is a "notify-on-completion" multiplexer. Currently a
proactor is only implemented on Windows with IOCP. proactor is only implemented on Windows with IOCP.
""" """
__all__ = ['BaseProactorEventLoop'] __all__ = 'BaseProactorEventLoop',
import io
import os
import socket import socket
import warnings import warnings
import signal
import threading
import collections
from . import base_events from . import base_events
from . import compat
from . import constants from . import constants
from . import futures from . import futures
from . import exceptions
from . import protocols
from . import sslproto from . import sslproto
from . import transports from . import transports
from . import trsock
from .log import logger from .log import logger
def _set_socket_extra(transport, sock):
transport._extra['socket'] = trsock.TransportSocket(sock)
try:
transport._extra['sockname'] = sock.getsockname()
except socket.error:
if transport._loop.get_debug():
logger.warning(
"getsockname() failed on %r", sock, exc_info=True)
if 'peername' not in transport._extra:
try:
transport._extra['peername'] = sock.getpeername()
except socket.error:
# UDP sockets may not have a peer name
transport._extra['peername'] = None
class _ProactorBasePipeTransport(transports._FlowControlMixin, class _ProactorBasePipeTransport(transports._FlowControlMixin,
transports.BaseTransport): transports.BaseTransport):
"""Base class for pipe and socket transports.""" """Base class for pipe and socket transports."""
@@ -27,7 +52,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
super().__init__(extra, loop) super().__init__(extra, loop)
self._set_extra(sock) self._set_extra(sock)
self._sock = sock self._sock = sock
self._protocol = protocol self.set_protocol(protocol)
self._server = server self._server = server
self._buffer = None # None or bytearray. self._buffer = None # None or bytearray.
self._read_fut = None self._read_fut = None
@@ -35,6 +60,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
self._pending_write = 0 self._pending_write = 0
self._conn_lost = 0 self._conn_lost = 0
self._closing = False # Set when close() called. self._closing = False # Set when close() called.
self._called_connection_lost = False
self._eof_written = False self._eof_written = False
if self._server is not None: if self._server is not None:
self._server._attach() self._server._attach()
@@ -51,17 +77,16 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
elif self._closing: elif self._closing:
info.append('closing') info.append('closing')
if self._sock is not None: if self._sock is not None:
info.append('fd=%s' % self._sock.fileno()) info.append(f'fd={self._sock.fileno()}')
if self._read_fut is not None: if self._read_fut is not None:
info.append('read=%s' % self._read_fut) info.append(f'read={self._read_fut!r}')
if self._write_fut is not None: if self._write_fut is not None:
info.append("write=%r" % self._write_fut) info.append(f'write={self._write_fut!r}')
if self._buffer: if self._buffer:
bufsize = len(self._buffer) info.append(f'write_bufsize={len(self._buffer)}')
info.append('write_bufsize=%s' % bufsize)
if self._eof_written: if self._eof_written:
info.append('EOF written') info.append('EOF written')
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
def _set_extra(self, sock): def _set_extra(self, sock):
self._extra['pipe'] = sock self._extra['pipe'] = sock
@@ -86,31 +111,33 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
self._read_fut.cancel() self._read_fut.cancel()
self._read_fut = None self._read_fut = None
# On Python 3.3 and older, objects with a destructor part of a reference def __del__(self, _warn=warnings.warn):
# cycle are never destroyed. It's not more the case on Python 3.4 thanks if self._sock is not None:
# to the PEP 442. _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
if compat.PY34: self._sock.close()
def __del__(self):
if self._sock is not None:
warnings.warn("unclosed transport %r" % self, ResourceWarning,
source=self)
self.close()
def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
if isinstance(exc, base_events._FATAL_ERROR_IGNORE): try:
if self._loop.get_debug(): if isinstance(exc, OSError):
logger.debug("%r: %s", self, message, exc_info=True) if self._loop.get_debug():
else: logger.debug("%r: %s", self, message, exc_info=True)
self._loop.call_exception_handler({ else:
'message': message, self._loop.call_exception_handler({
'exception': exc, 'message': message,
'transport': self, 'exception': exc,
'protocol': self._protocol, 'transport': self,
}) 'protocol': self._protocol,
self._force_close(exc) })
finally:
self._force_close(exc)
def _force_close(self, exc): def _force_close(self, exc):
if self._closing: if self._empty_waiter is not None and not self._empty_waiter.done():
if exc is None:
self._empty_waiter.set_result(None)
else:
self._empty_waiter.set_exception(exc)
if self._closing and self._called_connection_lost:
return return
self._closing = True self._closing = True
self._conn_lost += 1 self._conn_lost += 1
@@ -125,6 +152,8 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
self._loop.call_soon(self._call_connection_lost, exc) self._loop.call_soon(self._call_connection_lost, exc)
def _call_connection_lost(self, exc): def _call_connection_lost(self, exc):
if self._called_connection_lost:
return
try: try:
self._protocol.connection_lost(exc) self._protocol.connection_lost(exc)
finally: finally:
@@ -132,7 +161,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
# end then it may fail with ERROR_NETNAME_DELETED if we # end then it may fail with ERROR_NETNAME_DELETED if we
# just close our end. First calling shutdown() seems to # just close our end. First calling shutdown() seems to
# cure it, but maybe using DisconnectEx() would be better. # cure it, but maybe using DisconnectEx() would be better.
if hasattr(self._sock, 'shutdown'): if hasattr(self._sock, 'shutdown') and self._sock.fileno() != -1:
self._sock.shutdown(socket.SHUT_RDWR) self._sock.shutdown(socket.SHUT_RDWR)
self._sock.close() self._sock.close()
self._sock = None self._sock = None
@@ -140,6 +169,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
if server is not None: if server is not None:
server._detach() server._detach()
self._server = None self._server = None
self._called_connection_lost = True
def get_write_buffer_size(self): def get_write_buffer_size(self):
size = self._pending_write size = self._pending_write
@@ -153,53 +183,127 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
"""Transport for read pipes.""" """Transport for read pipes."""
def __init__(self, loop, sock, protocol, waiter=None, def __init__(self, loop, sock, protocol, waiter=None,
extra=None, server=None): extra=None, server=None, buffer_size=65536):
self._pending_data_length = -1
self._paused = True
super().__init__(loop, sock, protocol, waiter, extra, server) super().__init__(loop, sock, protocol, waiter, extra, server)
self._paused = False
self._data = bytearray(buffer_size)
self._loop.call_soon(self._loop_reading) self._loop.call_soon(self._loop_reading)
self._paused = False
def is_reading(self):
return not self._paused and not self._closing
def pause_reading(self): def pause_reading(self):
if self._closing: if self._closing or self._paused:
raise RuntimeError('Cannot pause_reading() when closing') return
if self._paused:
raise RuntimeError('Already paused')
self._paused = True self._paused = True
# bpo-33694: Don't cancel self._read_fut because cancelling an
# overlapped WSASend() loss silently data with the current proactor
# implementation.
#
# If CancelIoEx() fails with ERROR_NOT_FOUND, it means that WSASend()
# completed (even if HasOverlappedIoCompleted() returns 0), but
# Overlapped.cancel() currently silently ignores the ERROR_NOT_FOUND
# error. Once the overlapped is ignored, the IOCP loop will ignores the
# completion I/O event and so not read the result of the overlapped
# WSARecv().
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r pauses reading", self) logger.debug("%r pauses reading", self)
def resume_reading(self): def resume_reading(self):
if not self._paused: if self._closing or not self._paused:
raise RuntimeError('Not paused')
self._paused = False
if self._closing:
return return
self._loop.call_soon(self._loop_reading, self._read_fut)
self._paused = False
if self._read_fut is None:
self._loop.call_soon(self._loop_reading, None)
length = self._pending_data_length
self._pending_data_length = -1
if length > -1:
# Call the protocol method after calling _loop_reading(),
# since the protocol can decide to pause reading again.
self._loop.call_soon(self._data_received, self._data[:length], length)
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r resumes reading", self) logger.debug("%r resumes reading", self)
def _loop_reading(self, fut=None): def _eof_received(self):
if self._paused: if self._loop.get_debug():
return logger.debug("%r received EOF", self)
data = None
try:
keep_open = self._protocol.eof_received()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(
exc, 'Fatal error: protocol.eof_received() call failed.')
return
if not keep_open:
self.close()
def _data_received(self, data, length):
if self._paused:
# Don't call any protocol method while reading is paused.
# The protocol will be called on resume_reading().
assert self._pending_data_length == -1
self._pending_data_length = length
return
if length == 0:
self._eof_received()
return
if isinstance(self._protocol, protocols.BufferedProtocol):
try:
protocols._feed_data_to_buffered_proto(self._protocol, data)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc,
'Fatal error: protocol.buffer_updated() '
'call failed.')
return
else:
self._protocol.data_received(data)
def _loop_reading(self, fut=None):
length = -1
data = None
try: try:
if fut is not None: if fut is not None:
assert self._read_fut is fut or (self._read_fut is None and assert self._read_fut is fut or (self._read_fut is None and
self._closing) self._closing)
self._read_fut = None self._read_fut = None
data = fut.result() # deliver data later in "finally" clause if fut.done():
# deliver data later in "finally" clause
length = fut.result()
if length == 0:
# we got end-of-file so no need to reschedule a new read
return
# It's a new slice so make it immutable so protocols upstream don't have problems
data = bytes(memoryview(self._data)[:length])
else:
# the future will be replaced by next proactor.recv call
fut.cancel()
if self._closing: if self._closing:
# since close() has been called we ignore any read data # since close() has been called we ignore any read data
data = None
return return
if data == b'': # bpo-33694: buffer_updated() has currently no fast path because of
# we got end-of-file so no need to reschedule a new read # a data loss issue caused by overlapped WSASend() cancellation.
return
# reschedule a new read if not self._paused:
self._read_fut = self._loop._proactor.recv(self._sock, 4096) # reschedule a new read
self._read_fut = self._loop._proactor.recv_into(self._sock, self._data)
except ConnectionAbortedError as exc: except ConnectionAbortedError as exc:
if not self._closing: if not self._closing:
self._fatal_error(exc, 'Fatal read error on pipe transport') self._fatal_error(exc, 'Fatal read error on pipe transport')
@@ -210,32 +314,36 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
self._force_close(exc) self._force_close(exc)
except OSError as exc: except OSError as exc:
self._fatal_error(exc, 'Fatal read error on pipe transport') self._fatal_error(exc, 'Fatal read error on pipe transport')
except futures.CancelledError: except exceptions.CancelledError:
if not self._closing: if not self._closing:
raise raise
else: else:
self._read_fut.add_done_callback(self._loop_reading) if not self._paused:
self._read_fut.add_done_callback(self._loop_reading)
finally: finally:
if data: if length > -1:
self._protocol.data_received(data) self._data_received(data, length)
elif data is not None:
if self._loop.get_debug():
logger.debug("%r received EOF", self)
keep_open = self._protocol.eof_received()
if not keep_open:
self.close()
class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport, class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
transports.WriteTransport): transports.WriteTransport):
"""Transport for write pipes.""" """Transport for write pipes."""
_start_tls_compatible = True
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self._empty_waiter = None
def write(self, data): def write(self, data):
if not isinstance(data, (bytes, bytearray, memoryview)): if not isinstance(data, (bytes, bytearray, memoryview)):
raise TypeError('data argument must be byte-ish (%r)', raise TypeError(
type(data)) f"data argument must be a bytes-like object, "
f"not {type(data).__name__}")
if self._eof_written: if self._eof_written:
raise RuntimeError('write_eof() already called') raise RuntimeError('write_eof() already called')
if self._empty_waiter is not None:
raise RuntimeError('unable to write; sendfile is in progress')
if not data: if not data:
return return
@@ -267,6 +375,10 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
def _loop_writing(self, f=None, data=None): def _loop_writing(self, f=None, data=None):
try: try:
if f is not None and self._write_fut is None and self._closing:
# XXX most likely self._force_close() has been called, and
# it has set self._write_fut to None.
return
assert f is self._write_fut assert f is self._write_fut
self._write_fut = None self._write_fut = None
self._pending_write = 0 self._pending_write = 0
@@ -295,6 +407,8 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
self._maybe_pause_protocol() self._maybe_pause_protocol()
else: else:
self._write_fut.add_done_callback(self._loop_writing) self._write_fut.add_done_callback(self._loop_writing)
if self._empty_waiter is not None and self._write_fut is None:
self._empty_waiter.set_result(None)
except ConnectionResetError as exc: except ConnectionResetError as exc:
self._force_close(exc) self._force_close(exc)
except OSError as exc: except OSError as exc:
@@ -309,6 +423,17 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
def abort(self): def abort(self):
self._force_close(None) self._force_close(None)
def _make_empty_waiter(self):
if self._empty_waiter is not None:
raise RuntimeError("Empty waiter is already set")
self._empty_waiter = self._loop.create_future()
if self._write_fut is None:
self._empty_waiter.set_result(None)
return self._empty_waiter
def _reset_empty_waiter(self):
self._empty_waiter = None
class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport): class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
@@ -332,6 +457,138 @@ class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
self.close() self.close()
class _ProactorDatagramTransport(_ProactorBasePipeTransport,
transports.DatagramTransport):
max_size = 256 * 1024
def __init__(self, loop, sock, protocol, address=None,
waiter=None, extra=None):
self._address = address
self._empty_waiter = None
self._buffer_size = 0
# We don't need to call _protocol.connection_made() since our base
# constructor does it for us.
super().__init__(loop, sock, protocol, waiter=waiter, extra=extra)
# The base constructor sets _buffer = None, so we set it here
self._buffer = collections.deque()
self._loop.call_soon(self._loop_reading)
def _set_extra(self, sock):
_set_socket_extra(self, sock)
def get_write_buffer_size(self):
return self._buffer_size
def abort(self):
self._force_close(None)
def sendto(self, data, addr=None):
if not isinstance(data, (bytes, bytearray, memoryview)):
raise TypeError('data argument must be bytes-like object (%r)',
type(data))
if not data:
return
if self._address is not None and addr not in (None, self._address):
raise ValueError(
f'Invalid address: must be None or {self._address}')
if self._conn_lost and self._address:
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
logger.warning('socket.sendto() raised exception.')
self._conn_lost += 1
return
# Ensure that what we buffer is immutable.
self._buffer.append((bytes(data), addr))
self._buffer_size += len(data)
if self._write_fut is None:
# No current write operations are active, kick one off
self._loop_writing()
# else: A write operation is already kicked off
self._maybe_pause_protocol()
def _loop_writing(self, fut=None):
try:
if self._conn_lost:
return
assert fut is self._write_fut
self._write_fut = None
if fut:
# We are in a _loop_writing() done callback, get the result
fut.result()
if not self._buffer or (self._conn_lost and self._address):
# The connection has been closed
if self._closing:
self._loop.call_soon(self._call_connection_lost, None)
return
data, addr = self._buffer.popleft()
self._buffer_size -= len(data)
if self._address is not None:
self._write_fut = self._loop._proactor.send(self._sock,
data)
else:
self._write_fut = self._loop._proactor.sendto(self._sock,
data,
addr=addr)
except OSError as exc:
self._protocol.error_received(exc)
except Exception as exc:
self._fatal_error(exc, 'Fatal write error on datagram transport')
else:
self._write_fut.add_done_callback(self._loop_writing)
self._maybe_resume_protocol()
def _loop_reading(self, fut=None):
data = None
try:
if self._conn_lost:
return
assert self._read_fut is fut or (self._read_fut is None and
self._closing)
self._read_fut = None
if fut is not None:
res = fut.result()
if self._closing:
# since close() has been called we ignore any read data
data = None
return
if self._address is not None:
data, addr = res, self._address
else:
data, addr = res
if self._conn_lost:
return
if self._address is not None:
self._read_fut = self._loop._proactor.recv(self._sock,
self.max_size)
else:
self._read_fut = self._loop._proactor.recvfrom(self._sock,
self.max_size)
except OSError as exc:
self._protocol.error_received(exc)
except exceptions.CancelledError:
if not self._closing:
raise
else:
if self._read_fut is not None:
self._read_fut.add_done_callback(self._loop_reading)
finally:
if data:
self._protocol.datagram_received(data, addr)
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport, class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
_ProactorBaseWritePipeTransport, _ProactorBaseWritePipeTransport,
transports.Transport): transports.Transport):
@@ -349,21 +606,15 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport,
transports.Transport): transports.Transport):
"""Transport for connected sockets.""" """Transport for connected sockets."""
_sendfile_compatible = constants._SendfileMode.TRY_NATIVE
def __init__(self, loop, sock, protocol, waiter=None,
extra=None, server=None):
super().__init__(loop, sock, protocol, waiter, extra, server)
base_events._set_nodelay(sock)
def _set_extra(self, sock): def _set_extra(self, sock):
self._extra['socket'] = sock _set_socket_extra(self, sock)
try:
self._extra['sockname'] = sock.getsockname()
except (socket.error, AttributeError):
if self._loop.get_debug():
logger.warning("getsockname() failed on %r",
sock, exc_info=True)
if 'peername' not in self._extra:
try:
self._extra['peername'] = sock.getpeername()
except (socket.error, AttributeError):
if self._loop.get_debug():
logger.warning("getpeername() failed on %r",
sock, exc_info=True)
def can_write_eof(self): def can_write_eof(self):
return True return True
@@ -387,26 +638,35 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self._accept_futures = {} # socket file descriptor => Future self._accept_futures = {} # socket file descriptor => Future
proactor.set_loop(self) proactor.set_loop(self)
self._make_self_pipe() self._make_self_pipe()
if threading.current_thread() is threading.main_thread():
# wakeup fd can only be installed to a file descriptor from the main thread
signal.set_wakeup_fd(self._csock.fileno())
def _make_socket_transport(self, sock, protocol, waiter=None, def _make_socket_transport(self, sock, protocol, waiter=None,
extra=None, server=None): extra=None, server=None):
return _ProactorSocketTransport(self, sock, protocol, waiter, return _ProactorSocketTransport(self, sock, protocol, waiter,
extra, server) extra, server)
def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None, def _make_ssl_transport(
*, server_side=False, server_hostname=None, self, rawsock, protocol, sslcontext, waiter=None,
extra=None, server=None): *, server_side=False, server_hostname=None,
if not sslproto._is_sslproto_available(): extra=None, server=None,
raise NotImplementedError("Proactor event loop requires Python 3.5" ssl_handshake_timeout=None,
" or newer (ssl.MemoryBIO) to support " ssl_shutdown_timeout=None):
"SSL") ssl_protocol = sslproto.SSLProtocol(
self, protocol, sslcontext, waiter,
ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter, server_side, server_hostname,
server_side, server_hostname) ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout)
_ProactorSocketTransport(self, rawsock, ssl_protocol, _ProactorSocketTransport(self, rawsock, ssl_protocol,
extra=extra, server=server) extra=extra, server=server)
return ssl_protocol._app_transport return ssl_protocol._app_transport
def _make_datagram_transport(self, sock, protocol,
address=None, waiter=None, extra=None):
return _ProactorDatagramTransport(self, sock, protocol, address,
waiter, extra)
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None, def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
extra=None): extra=None):
return _ProactorDuplexPipeTransport(self, return _ProactorDuplexPipeTransport(self,
@@ -428,6 +688,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
if self.is_closed(): if self.is_closed():
return return
if threading.current_thread() is threading.main_thread():
signal.set_wakeup_fd(-1)
# Call these methods before closing the event loop (before calling # Call these methods before closing the event loop (before calling
# BaseEventLoop.close), because they can schedule callbacks with # BaseEventLoop.close), because they can schedule callbacks with
# call_soon(), which is forbidden when the event loop is closed. # call_soon(), which is forbidden when the event loop is closed.
@@ -440,20 +702,73 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
# Close the event loop # Close the event loop
super().close() super().close()
def sock_recv(self, sock, n): async def sock_recv(self, sock, n):
return self._proactor.recv(sock, n) return await self._proactor.recv(sock, n)
def sock_sendall(self, sock, data): async def sock_recv_into(self, sock, buf):
return self._proactor.send(sock, data) return await self._proactor.recv_into(sock, buf)
def sock_connect(self, sock, address): async def sock_recvfrom(self, sock, bufsize):
return self._proactor.connect(sock, address) return await self._proactor.recvfrom(sock, bufsize)
def sock_accept(self, sock): async def sock_recvfrom_into(self, sock, buf, nbytes=0):
return self._proactor.accept(sock) if not nbytes:
nbytes = len(buf)
def _socketpair(self): return await self._proactor.recvfrom_into(sock, buf, nbytes)
raise NotImplementedError
async def sock_sendall(self, sock, data):
return await self._proactor.send(sock, data)
async def sock_sendto(self, sock, data, address):
return await self._proactor.sendto(sock, data, 0, address)
async def sock_connect(self, sock, address):
return await self._proactor.connect(sock, address)
async def sock_accept(self, sock):
return await self._proactor.accept(sock)
async def _sock_sendfile_native(self, sock, file, offset, count):
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
raise exceptions.SendfileNotAvailableError("not a regular file")
try:
fsize = os.fstat(fileno).st_size
except OSError:
raise exceptions.SendfileNotAvailableError("not a regular file")
blocksize = count if count else fsize
if not blocksize:
return 0 # empty file
blocksize = min(blocksize, 0xffff_ffff)
end_pos = min(offset + count, fsize) if count else fsize
offset = min(offset, fsize)
total_sent = 0
try:
while True:
blocksize = min(end_pos - offset, blocksize)
if blocksize <= 0:
return total_sent
await self._proactor.sendfile(sock, file, offset, blocksize)
offset += blocksize
total_sent += blocksize
finally:
if total_sent > 0:
file.seek(offset)
async def _sendfile_native(self, transp, file, offset, count):
resume_reading = transp.is_reading()
transp.pause_reading()
await transp._make_empty_waiter()
try:
return await self.sock_sendfile(transp._sock, file, offset, count,
fallback=False)
finally:
transp._reset_empty_waiter()
if resume_reading:
transp.resume_reading()
def _close_self_pipe(self): def _close_self_pipe(self):
if self._self_reading_future is not None: if self._self_reading_future is not None:
@@ -467,21 +782,30 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
def _make_self_pipe(self): def _make_self_pipe(self):
# A self-socket, really. :-) # A self-socket, really. :-)
self._ssock, self._csock = self._socketpair() self._ssock, self._csock = socket.socketpair()
self._ssock.setblocking(False) self._ssock.setblocking(False)
self._csock.setblocking(False) self._csock.setblocking(False)
self._internal_fds += 1 self._internal_fds += 1
self.call_soon(self._loop_self_reading)
def _loop_self_reading(self, f=None): def _loop_self_reading(self, f=None):
try: try:
if f is not None: if f is not None:
f.result() # may raise f.result() # may raise
if self._self_reading_future is not f:
# When we scheduled this Future, we assigned it to
# _self_reading_future. If it's not there now, something has
# tried to cancel the loop while this callback was still in the
# queue (see windows_events.ProactorEventLoop.run_forever). In
# that case stop here instead of continuing to schedule a new
# iteration.
return
f = self._proactor.recv(self._ssock, 4096) f = self._proactor.recv(self._ssock, 4096)
except futures.CancelledError: except exceptions.CancelledError:
# _close_self_pipe() has been called, stop waiting for data # _close_self_pipe() has been called, stop waiting for data
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self.call_exception_handler({ self.call_exception_handler({
'message': 'Error on reading from the event loop self pipe', 'message': 'Error on reading from the event loop self pipe',
'exception': exc, 'exception': exc,
@@ -492,10 +816,27 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
f.add_done_callback(self._loop_self_reading) f.add_done_callback(self._loop_self_reading)
def _write_to_self(self): def _write_to_self(self):
self._csock.send(b'\0') # This may be called from a different thread, possibly after
# _close_self_pipe() has been called or even while it is
# running. Guard for self._csock being None or closed. When
# a socket is closed, send() raises OSError (with errno set to
# EBADF, but let's not rely on the exact error code).
csock = self._csock
if csock is None:
return
try:
csock.send(b'\0')
except OSError:
if self._debug:
logger.debug("Fail to write a null byte into the "
"self-pipe socket",
exc_info=True)
def _start_serving(self, protocol_factory, sock, def _start_serving(self, protocol_factory, sock,
sslcontext=None, server=None, backlog=100): sslcontext=None, server=None, backlog=100,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
def loop(f=None): def loop(f=None):
try: try:
@@ -508,7 +849,9 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
if sslcontext is not None: if sslcontext is not None:
self._make_ssl_transport( self._make_ssl_transport(
conn, protocol, sslcontext, server_side=True, conn, protocol, sslcontext, server_side=True,
extra={'peername': addr}, server=server) extra={'peername': addr}, server=server,
ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout)
else: else:
self._make_socket_transport( self._make_socket_transport(
conn, protocol, conn, protocol,
@@ -521,13 +864,13 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self.call_exception_handler({ self.call_exception_handler({
'message': 'Accept failed on a socket', 'message': 'Accept failed on a socket',
'exception': exc, 'exception': exc,
'socket': sock, 'socket': trsock.TransportSocket(sock),
}) })
sock.close() sock.close()
elif self._debug: elif self._debug:
logger.debug("Accept failed on socket %r", logger.debug("Accept failed on socket %r",
sock, exc_info=True) sock, exc_info=True)
except futures.CancelledError: except exceptions.CancelledError:
sock.close() sock.close()
else: else:
self._accept_futures[sock.fileno()] = f self._accept_futures[sock.fileno()] = f
@@ -545,6 +888,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self._accept_futures.clear() self._accept_futures.clear()
def _stop_serving(self, sock): def _stop_serving(self, sock):
self._stop_accept_futures() future = self._accept_futures.pop(sock.fileno(), None)
if future:
future.cancel()
self._proactor._stop_serving(sock) self._proactor._stop_serving(sock)
sock.close() sock.close()

View File

@@ -1,7 +1,9 @@
"""Abstract Protocol class.""" """Abstract Protocol base classes."""
__all__ = ['BaseProtocol', 'Protocol', 'DatagramProtocol', __all__ = (
'SubprocessProtocol'] 'BaseProtocol', 'Protocol', 'DatagramProtocol',
'SubprocessProtocol', 'BufferedProtocol',
)
class BaseProtocol: class BaseProtocol:
@@ -14,6 +16,8 @@ class BaseProtocol:
write-only transport like write pipe write-only transport like write pipe
""" """
__slots__ = ()
def connection_made(self, transport): def connection_made(self, transport):
"""Called when a connection is made. """Called when a connection is made.
@@ -85,6 +89,8 @@ class Protocol(BaseProtocol):
* CL: connection_lost() * CL: connection_lost()
""" """
__slots__ = ()
def data_received(self, data): def data_received(self, data):
"""Called when some data is received. """Called when some data is received.
@@ -100,9 +106,64 @@ class Protocol(BaseProtocol):
""" """
class BufferedProtocol(BaseProtocol):
"""Interface for stream protocol with manual buffer control.
Event methods, such as `create_server` and `create_connection`,
accept factories that return protocols that implement this interface.
The idea of BufferedProtocol is that it allows to manually allocate
and control the receive buffer. Event loops can then use the buffer
provided by the protocol to avoid unnecessary data copies. This
can result in noticeable performance improvement for protocols that
receive big amounts of data. Sophisticated protocols can allocate
the buffer only once at creation time.
State machine of calls:
start -> CM [-> GB [-> BU?]]* [-> ER?] -> CL -> end
* CM: connection_made()
* GB: get_buffer()
* BU: buffer_updated()
* ER: eof_received()
* CL: connection_lost()
"""
__slots__ = ()
def get_buffer(self, sizehint):
"""Called to allocate a new receive buffer.
*sizehint* is a recommended minimal size for the returned
buffer. When set to -1, the buffer size can be arbitrary.
Must return an object that implements the
:ref:`buffer protocol <bufferobjects>`.
It is an error to return a zero-sized buffer.
"""
def buffer_updated(self, nbytes):
"""Called when the buffer was updated with the received data.
*nbytes* is the total number of bytes that were written to
the buffer.
"""
def eof_received(self):
"""Called when the other end calls write_eof() or equivalent.
If this returns a false value (including None), the transport
will close itself. If it returns a true value, closing the
transport is up to the protocol.
"""
class DatagramProtocol(BaseProtocol): class DatagramProtocol(BaseProtocol):
"""Interface for datagram protocol.""" """Interface for datagram protocol."""
__slots__ = ()
def datagram_received(self, data, addr): def datagram_received(self, data, addr):
"""Called when some datagram is received.""" """Called when some datagram is received."""
@@ -116,6 +177,8 @@ class DatagramProtocol(BaseProtocol):
class SubprocessProtocol(BaseProtocol): class SubprocessProtocol(BaseProtocol):
"""Interface for protocol for subprocess calls.""" """Interface for protocol for subprocess calls."""
__slots__ = ()
def pipe_data_received(self, fd, data): def pipe_data_received(self, fd, data):
"""Called when the subprocess writes data into stdout/stderr pipe. """Called when the subprocess writes data into stdout/stderr pipe.
@@ -132,3 +195,22 @@ class SubprocessProtocol(BaseProtocol):
def process_exited(self): def process_exited(self):
"""Called when subprocess has exited.""" """Called when subprocess has exited."""
def _feed_data_to_buffered_proto(proto, data):
data_len = len(data)
while data_len:
buf = proto.get_buffer(data_len)
buf_len = len(buf)
if not buf_len:
raise RuntimeError('get_buffer() returned an empty buffer')
if buf_len >= data_len:
buf[:data_len] = data
proto.buffer_updated(data_len)
return
else:
buf[:buf_len] = data[:buf_len]
proto.buffer_updated(buf_len)
data = data[buf_len:]
data_len = len(data)

90
Lib/asyncio/queues.py vendored
View File

@@ -1,35 +1,28 @@
"""Queues""" __all__ = ('Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty')
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty']
import collections import collections
import heapq import heapq
from types import GenericAlias
from . import compat
from . import events
from . import locks from . import locks
from .coroutines import coroutine from . import mixins
class QueueEmpty(Exception): class QueueEmpty(Exception):
"""Exception raised when Queue.get_nowait() is called on a Queue object """Raised when Queue.get_nowait() is called on an empty Queue."""
which is empty.
"""
pass pass
class QueueFull(Exception): class QueueFull(Exception):
"""Exception raised when the Queue.put_nowait() method is called on a Queue """Raised when the Queue.put_nowait() method is called on a full Queue."""
object which is full.
"""
pass pass
class Queue: class Queue(mixins._LoopBoundMixin):
"""A queue, useful for coordinating producer and consumer coroutines. """A queue, useful for coordinating producer and consumer coroutines.
If maxsize is less than or equal to zero, the queue size is infinite. If it If maxsize is less than or equal to zero, the queue size is infinite. If it
is an integer greater than 0, then "yield from put()" will block when the is an integer greater than 0, then "await put()" will block when the
queue reaches maxsize, until an item is removed by get(). queue reaches maxsize, until an item is removed by get().
Unlike the standard library Queue, you can reliably know this Queue's size Unlike the standard library Queue, you can reliably know this Queue's size
@@ -37,11 +30,7 @@ class Queue:
interrupted between calling qsize() and doing an operation on the Queue. interrupted between calling qsize() and doing an operation on the Queue.
""" """
def __init__(self, maxsize=0, *, loop=None): def __init__(self, maxsize=0):
if loop is None:
self._loop = events.get_event_loop()
else:
self._loop = loop
self._maxsize = maxsize self._maxsize = maxsize
# Futures. # Futures.
@@ -49,7 +38,7 @@ class Queue:
# Futures. # Futures.
self._putters = collections.deque() self._putters = collections.deque()
self._unfinished_tasks = 0 self._unfinished_tasks = 0
self._finished = locks.Event(loop=self._loop) self._finished = locks.Event()
self._finished.set() self._finished.set()
self._init(maxsize) self._init(maxsize)
@@ -75,25 +64,23 @@ class Queue:
break break
def __repr__(self): def __repr__(self):
return '<{} at {:#x} {}>'.format( return f'<{type(self).__name__} at {id(self):#x} {self._format()}>'
type(self).__name__, id(self), self._format())
def __str__(self): def __str__(self):
return '<{} {}>'.format(type(self).__name__, self._format()) return f'<{type(self).__name__} {self._format()}>'
def __class_getitem__(cls, type): __class_getitem__ = classmethod(GenericAlias)
return cls
def _format(self): def _format(self):
result = 'maxsize={!r}'.format(self._maxsize) result = f'maxsize={self._maxsize!r}'
if getattr(self, '_queue', None): if getattr(self, '_queue', None):
result += ' _queue={!r}'.format(list(self._queue)) result += f' _queue={list(self._queue)!r}'
if self._getters: if self._getters:
result += ' _getters[{}]'.format(len(self._getters)) result += f' _getters[{len(self._getters)}]'
if self._putters: if self._putters:
result += ' _putters[{}]'.format(len(self._putters)) result += f' _putters[{len(self._putters)}]'
if self._unfinished_tasks: if self._unfinished_tasks:
result += ' tasks={}'.format(self._unfinished_tasks) result += f' tasks={self._unfinished_tasks}'
return result return result
def qsize(self): def qsize(self):
@@ -120,22 +107,26 @@ class Queue:
else: else:
return self.qsize() >= self._maxsize return self.qsize() >= self._maxsize
@coroutine async def put(self, item):
def put(self, item):
"""Put an item into the queue. """Put an item into the queue.
Put an item into the queue. If the queue is full, wait until a free Put an item into the queue. If the queue is full, wait until a free
slot is available before adding item. slot is available before adding item.
This method is a coroutine.
""" """
while self.full(): while self.full():
putter = self._loop.create_future() putter = self._get_loop().create_future()
self._putters.append(putter) self._putters.append(putter)
try: try:
yield from putter await putter
except: except:
putter.cancel() # Just in case putter is not done yet. putter.cancel() # Just in case putter is not done yet.
try:
# Clean self._putters from canceled putters.
self._putters.remove(putter)
except ValueError:
# The putter could be removed from self._putters by a
# previous get_nowait call.
pass
if not self.full() and not putter.cancelled(): if not self.full() and not putter.cancelled():
# We were woken up by get_nowait(), but can't take # We were woken up by get_nowait(), but can't take
# the call. Wake up the next in line. # the call. Wake up the next in line.
@@ -155,21 +146,25 @@ class Queue:
self._finished.clear() self._finished.clear()
self._wakeup_next(self._getters) self._wakeup_next(self._getters)
@coroutine async def get(self):
def get(self):
"""Remove and return an item from the queue. """Remove and return an item from the queue.
If queue is empty, wait until an item is available. If queue is empty, wait until an item is available.
This method is a coroutine.
""" """
while self.empty(): while self.empty():
getter = self._loop.create_future() getter = self._get_loop().create_future()
self._getters.append(getter) self._getters.append(getter)
try: try:
yield from getter await getter
except: except:
getter.cancel() # Just in case getter is not done yet. getter.cancel() # Just in case getter is not done yet.
try:
# Clean self._getters from canceled getters.
self._getters.remove(getter)
except ValueError:
# The getter could be removed from self._getters by a
# previous put_nowait call.
pass
if not self.empty() and not getter.cancelled(): if not self.empty() and not getter.cancelled():
# We were woken up by put_nowait(), but can't take # We were woken up by put_nowait(), but can't take
# the call. Wake up the next in line. # the call. Wake up the next in line.
@@ -208,8 +203,7 @@ class Queue:
if self._unfinished_tasks == 0: if self._unfinished_tasks == 0:
self._finished.set() self._finished.set()
@coroutine async def join(self):
def join(self):
"""Block until all items in the queue have been gotten and processed. """Block until all items in the queue have been gotten and processed.
The count of unfinished tasks goes up whenever an item is added to the The count of unfinished tasks goes up whenever an item is added to the
@@ -218,7 +212,7 @@ class Queue:
When the count of unfinished tasks drops to zero, join() unblocks. When the count of unfinished tasks drops to zero, join() unblocks.
""" """
if self._unfinished_tasks > 0: if self._unfinished_tasks > 0:
yield from self._finished.wait() await self._finished.wait()
class PriorityQueue(Queue): class PriorityQueue(Queue):
@@ -248,9 +242,3 @@ class LifoQueue(Queue):
def _get(self): def _get(self):
return self._queue.pop() return self._queue.pop()
if not compat.PY35:
JoinableQueue = Queue
"""Deprecated alias for Queue."""
__all__.append('JoinableQueue')

187
Lib/asyncio/runners.py vendored
View File

@@ -1,16 +1,168 @@
__all__ = ['run'] __all__ = ('Runner', 'run')
import contextvars
import enum
import functools
import threading
import signal
from . import coroutines from . import coroutines
from . import events from . import events
from . import exceptions
from . import tasks from . import tasks
from . import constants
class _State(enum.Enum):
CREATED = "created"
INITIALIZED = "initialized"
CLOSED = "closed"
def run(main, *, debug=False): class Runner:
"""Run a coroutine. """A context manager that controls event loop life cycle.
The context manager always creates a new event loop,
allows to run async functions inside it,
and properly finalizes the loop at the context manager exit.
If debug is True, the event loop will be run in debug mode.
If loop_factory is passed, it is used for new event loop creation.
asyncio.run(main(), debug=True)
is a shortcut for
with asyncio.Runner(debug=True) as runner:
runner.run(main())
The run() method can be called multiple times within the runner's context.
This can be useful for interactive console (e.g. IPython),
unittest runners, console tools, -- everywhere when async code
is called from existing sync framework and where the preferred single
asyncio.run() call doesn't work.
"""
# Note: the class is final, it is not intended for inheritance.
def __init__(self, *, debug=None, loop_factory=None):
self._state = _State.CREATED
self._debug = debug
self._loop_factory = loop_factory
self._loop = None
self._context = None
self._interrupt_count = 0
self._set_event_loop = False
def __enter__(self):
self._lazy_init()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
"""Shutdown and close event loop."""
if self._state is not _State.INITIALIZED:
return
try:
loop = self._loop
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
loop.run_until_complete(
loop.shutdown_default_executor(constants.THREAD_JOIN_TIMEOUT))
finally:
if self._set_event_loop:
events.set_event_loop(None)
loop.close()
self._loop = None
self._state = _State.CLOSED
def get_loop(self):
"""Return embedded event loop."""
self._lazy_init()
return self._loop
def run(self, coro, *, context=None):
"""Run a coroutine inside the embedded event loop."""
if not coroutines.iscoroutine(coro):
raise ValueError("a coroutine was expected, got {!r}".format(coro))
if events._get_running_loop() is not None:
# fail fast with short traceback
raise RuntimeError(
"Runner.run() cannot be called from a running event loop")
self._lazy_init()
if context is None:
context = self._context
task = self._loop.create_task(coro, context=context)
if (threading.current_thread() is threading.main_thread()
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
):
sigint_handler = functools.partial(self._on_sigint, main_task=task)
try:
signal.signal(signal.SIGINT, sigint_handler)
except ValueError:
# `signal.signal` may throw if `threading.main_thread` does
# not support signals (e.g. embedded interpreter with signals
# not registered - see gh-91880)
sigint_handler = None
else:
sigint_handler = None
self._interrupt_count = 0
try:
return self._loop.run_until_complete(task)
except exceptions.CancelledError:
if self._interrupt_count > 0:
uncancel = getattr(task, "uncancel", None)
if uncancel is not None and uncancel() == 0:
raise KeyboardInterrupt()
raise # CancelledError
finally:
if (sigint_handler is not None
and signal.getsignal(signal.SIGINT) is sigint_handler
):
signal.signal(signal.SIGINT, signal.default_int_handler)
def _lazy_init(self):
if self._state is _State.CLOSED:
raise RuntimeError("Runner is closed")
if self._state is _State.INITIALIZED:
return
if self._loop_factory is None:
self._loop = events.new_event_loop()
if not self._set_event_loop:
# Call set_event_loop only once to avoid calling
# attach_loop multiple times on child watchers
events.set_event_loop(self._loop)
self._set_event_loop = True
else:
self._loop = self._loop_factory()
if self._debug is not None:
self._loop.set_debug(self._debug)
self._context = contextvars.copy_context()
self._state = _State.INITIALIZED
def _on_sigint(self, signum, frame, main_task):
self._interrupt_count += 1
if self._interrupt_count == 1 and not main_task.done():
main_task.cancel()
# wakeup loop if it is blocked by select() with long timeout
self._loop.call_soon_threadsafe(lambda: None)
return
raise KeyboardInterrupt()
def run(main, *, debug=None, loop_factory=None):
"""Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous managing the asyncio event loop, finalizing asynchronous
generators. generators and closing the default executor.
This function cannot be called when another asyncio event loop is This function cannot be called when another asyncio event loop is
running in the same thread. running in the same thread.
@@ -21,6 +173,10 @@ def run(main, *, debug=False):
It should be used as a main entry point for asyncio programs, and should It should be used as a main entry point for asyncio programs, and should
ideally only be called once. ideally only be called once.
The executor is given a timeout duration of 5 minutes to shutdown.
If the executor hasn't finished within that duration, a warning is
emitted and the executor is closed.
Example: Example:
async def main(): async def main():
@@ -30,24 +186,12 @@ def run(main, *, debug=False):
asyncio.run(main()) asyncio.run(main())
""" """
if events._get_running_loop() is not None: if events._get_running_loop() is not None:
# fail fast with short traceback
raise RuntimeError( raise RuntimeError(
"asyncio.run() cannot be called from a running event loop") "asyncio.run() cannot be called from a running event loop")
if not coroutines.iscoroutine(main): with Runner(debug=debug, loop_factory=loop_factory) as runner:
raise ValueError("a coroutine was expected, got {!r}".format(main)) return runner.run(main)
loop = events.new_event_loop()
try:
events.set_event_loop(loop)
loop.set_debug(debug)
return loop.run_until_complete(main)
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()
def _cancel_all_tasks(loop): def _cancel_all_tasks(loop):
@@ -58,8 +202,7 @@ def _cancel_all_tasks(loop):
for task in to_cancel: for task in to_cancel:
task.cancel() task.cancel()
loop.run_until_complete( loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
for task in to_cancel: for task in to_cancel:
if task.cancelled(): if task.cancelled():

File diff suppressed because it is too large Load Diff

1136
Lib/asyncio/sslproto.py vendored

File diff suppressed because it is too large Load Diff

149
Lib/asyncio/staggered.py vendored Normal file
View File

@@ -0,0 +1,149 @@
"""Support for running coroutines in parallel with staggered start times."""
__all__ = 'staggered_race',
import contextlib
import typing
from . import events
from . import exceptions as exceptions_mod
from . import locks
from . import tasks
async def staggered_race(
coro_fns: typing.Iterable[typing.Callable[[], typing.Awaitable]],
delay: typing.Optional[float],
*,
loop: events.AbstractEventLoop = None,
) -> typing.Tuple[
typing.Any,
typing.Optional[int],
typing.List[typing.Optional[Exception]]
]:
"""Run coroutines with staggered start times and take the first to finish.
This method takes an iterable of coroutine functions. The first one is
started immediately. From then on, whenever the immediately preceding one
fails (raises an exception), or when *delay* seconds has passed, the next
coroutine is started. This continues until one of the coroutines complete
successfully, in which case all others are cancelled, or until all
coroutines fail.
The coroutines provided should be well-behaved in the following way:
* They should only ``return`` if completed successfully.
* They should always raise an exception if they did not complete
successfully. In particular, if they handle cancellation, they should
probably reraise, like this::
try:
# do work
except asyncio.CancelledError:
# undo partially completed work
raise
Args:
coro_fns: an iterable of coroutine functions, i.e. callables that
return a coroutine object when called. Use ``functools.partial`` or
lambdas to pass arguments.
delay: amount of time, in seconds, between starting coroutines. If
``None``, the coroutines will run sequentially.
loop: the event loop to use.
Returns:
tuple *(winner_result, winner_index, exceptions)* where
- *winner_result*: the result of the winning coroutine, or ``None``
if no coroutines won.
- *winner_index*: the index of the winning coroutine in
``coro_fns``, or ``None`` if no coroutines won. If the winning
coroutine may return None on success, *winner_index* can be used
to definitively determine whether any coroutine won.
- *exceptions*: list of exceptions returned by the coroutines.
``len(exceptions)`` is equal to the number of coroutines actually
started, and the order is the same as in ``coro_fns``. The winning
coroutine's entry is ``None``.
"""
# TODO: when we have aiter() and anext(), allow async iterables in coro_fns.
loop = loop or events.get_running_loop()
enum_coro_fns = enumerate(coro_fns)
winner_result = None
winner_index = None
exceptions = []
running_tasks = []
async def run_one_coro(
previous_failed: typing.Optional[locks.Event]) -> None:
# Wait for the previous task to finish, or for delay seconds
if previous_failed is not None:
with contextlib.suppress(exceptions_mod.TimeoutError):
# Use asyncio.wait_for() instead of asyncio.wait() here, so
# that if we get cancelled at this point, Event.wait() is also
# cancelled, otherwise there will be a "Task destroyed but it is
# pending" later.
await tasks.wait_for(previous_failed.wait(), delay)
# Get the next coroutine to run
try:
this_index, coro_fn = next(enum_coro_fns)
except StopIteration:
return
# Start task that will run the next coroutine
this_failed = locks.Event()
next_task = loop.create_task(run_one_coro(this_failed))
running_tasks.append(next_task)
assert len(running_tasks) == this_index + 2
# Prepare place to put this coroutine's exceptions if not won
exceptions.append(None)
assert len(exceptions) == this_index + 1
try:
result = await coro_fn()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as e:
exceptions[this_index] = e
this_failed.set() # Kickstart the next coroutine
else:
# Store winner's results
nonlocal winner_index, winner_result
assert winner_index is None
winner_index = this_index
winner_result = result
# Cancel all other tasks. We take care to not cancel the current
# task as well. If we do so, then since there is no `await` after
# here and CancelledError are usually thrown at one, we will
# encounter a curious corner case where the current task will end
# up as done() == True, cancelled() == False, exception() ==
# asyncio.CancelledError. This behavior is specified in
# https://bugs.python.org/issue30048
for i, t in enumerate(running_tasks):
if i != this_index:
t.cancel()
first_task = loop.create_task(run_one_coro(None))
running_tasks.append(first_task)
try:
# Wait for a growing list of tasks to all finish: poor man's version of
# curio's TaskGroup or trio's nursery
done_count = 0
while done_count != len(running_tasks):
done, _ = await tasks.wait(running_tasks)
done_count = len(done)
# If run_one_coro raises an unhandled exception, it's probably a
# programming error, and I want to see it.
if __debug__:
for d in done:
if d.done() and not d.cancelled() and d.exception():
raise d.exception()
return winner_result, winner_index, exceptions
finally:
# Make sure no tasks are left running if we leave this function
for t in running_tasks:
t.cancel()

411
Lib/asyncio/streams.py vendored
View File

@@ -1,55 +1,30 @@
"""Stream-related things.""" __all__ = (
'StreamReader', 'StreamWriter', 'StreamReaderProtocol',
__all__ = ['StreamReader', 'StreamWriter', 'StreamReaderProtocol', 'open_connection', 'start_server')
'open_connection', 'start_server',
'IncompleteReadError',
'LimitOverrunError',
]
import collections
import socket import socket
import sys
import warnings
import weakref
if hasattr(socket, 'AF_UNIX'): if hasattr(socket, 'AF_UNIX'):
__all__.extend(['open_unix_connection', 'start_unix_server']) __all__ += ('open_unix_connection', 'start_unix_server')
from . import coroutines from . import coroutines
from . import compat
from . import events from . import events
from . import exceptions
from . import format_helpers
from . import protocols from . import protocols
from .coroutines import coroutine
from .log import logger from .log import logger
from .tasks import sleep
_DEFAULT_LIMIT = 2 ** 16 _DEFAULT_LIMIT = 2 ** 16 # 64 KiB
class IncompleteReadError(EOFError): async def open_connection(host=None, port=None, *,
""" limit=_DEFAULT_LIMIT, **kwds):
Incomplete read error. Attributes:
- partial: read bytes string before the end of stream was reached
- expected: total number of expected bytes (or None if unknown)
"""
def __init__(self, partial, expected):
super().__init__("%d bytes read on a total of %r expected bytes"
% (len(partial), expected))
self.partial = partial
self.expected = expected
class LimitOverrunError(Exception):
"""Reached the buffer limit while looking for a separator.
Attributes:
- consumed: total number of to be consumed bytes.
"""
def __init__(self, message, consumed):
super().__init__(message)
self.consumed = consumed
@coroutine
def open_connection(host=None, port=None, *,
loop=None, limit=_DEFAULT_LIMIT, **kwds):
"""A wrapper for create_connection() returning a (reader, writer) pair. """A wrapper for create_connection() returning a (reader, writer) pair.
The reader returned is a StreamReader instance; the writer is a The reader returned is a StreamReader instance; the writer is a
@@ -67,19 +42,17 @@ def open_connection(host=None, port=None, *,
StreamReaderProtocol classes, just copy the code -- there's StreamReaderProtocol classes, just copy the code -- there's
really nothing special here except some convenience.) really nothing special here except some convenience.)
""" """
if loop is None: loop = events.get_running_loop()
loop = events.get_event_loop()
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
protocol = StreamReaderProtocol(reader, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop)
transport, _ = yield from loop.create_connection( transport, _ = await loop.create_connection(
lambda: protocol, host, port, **kwds) lambda: protocol, host, port, **kwds)
writer = StreamWriter(transport, protocol, reader, loop) writer = StreamWriter(transport, protocol, reader, loop)
return reader, writer return reader, writer
@coroutine async def start_server(client_connected_cb, host=None, port=None, *,
def start_server(client_connected_cb, host=None, port=None, *, limit=_DEFAULT_LIMIT, **kwds):
loop=None, limit=_DEFAULT_LIMIT, **kwds):
"""Start a socket server, call back for each client connected. """Start a socket server, call back for each client connected.
The first parameter, `client_connected_cb`, takes two parameters: The first parameter, `client_connected_cb`, takes two parameters:
@@ -94,15 +67,13 @@ def start_server(client_connected_cb, host=None, port=None, *,
positional host and port, with various optional keyword arguments positional host and port, with various optional keyword arguments
following. The return value is the same as loop.create_server(). following. The return value is the same as loop.create_server().
Additional optional keyword arguments are loop (to set the event loop Additional optional keyword argument is limit (to set the buffer
instance to use) and limit (to set the buffer limit passed to the limit passed to the StreamReader).
StreamReader).
The return value is the same as loop.create_server(), i.e. a The return value is the same as loop.create_server(), i.e. a
Server object which can be used to stop the service. Server object which can be used to stop the service.
""" """
if loop is None: loop = events.get_running_loop()
loop = events.get_event_loop()
def factory(): def factory():
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
@@ -110,31 +81,28 @@ def start_server(client_connected_cb, host=None, port=None, *,
loop=loop) loop=loop)
return protocol return protocol
return (yield from loop.create_server(factory, host, port, **kwds)) return await loop.create_server(factory, host, port, **kwds)
if hasattr(socket, 'AF_UNIX'): if hasattr(socket, 'AF_UNIX'):
# UNIX Domain Sockets are supported on this platform # UNIX Domain Sockets are supported on this platform
@coroutine async def open_unix_connection(path=None, *,
def open_unix_connection(path=None, *, limit=_DEFAULT_LIMIT, **kwds):
loop=None, limit=_DEFAULT_LIMIT, **kwds):
"""Similar to `open_connection` but works with UNIX Domain Sockets.""" """Similar to `open_connection` but works with UNIX Domain Sockets."""
if loop is None: loop = events.get_running_loop()
loop = events.get_event_loop()
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
protocol = StreamReaderProtocol(reader, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop)
transport, _ = yield from loop.create_unix_connection( transport, _ = await loop.create_unix_connection(
lambda: protocol, path, **kwds) lambda: protocol, path, **kwds)
writer = StreamWriter(transport, protocol, reader, loop) writer = StreamWriter(transport, protocol, reader, loop)
return reader, writer return reader, writer
@coroutine async def start_unix_server(client_connected_cb, path=None, *,
def start_unix_server(client_connected_cb, path=None, *, limit=_DEFAULT_LIMIT, **kwds):
loop=None, limit=_DEFAULT_LIMIT, **kwds):
"""Similar to `start_server` but works with UNIX Domain Sockets.""" """Similar to `start_server` but works with UNIX Domain Sockets."""
if loop is None: loop = events.get_running_loop()
loop = events.get_event_loop()
def factory(): def factory():
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
@@ -142,14 +110,14 @@ if hasattr(socket, 'AF_UNIX'):
loop=loop) loop=loop)
return protocol return protocol
return (yield from loop.create_unix_server(factory, path, **kwds)) return await loop.create_unix_server(factory, path, **kwds)
class FlowControlMixin(protocols.Protocol): class FlowControlMixin(protocols.Protocol):
"""Reusable flow control logic for StreamWriter.drain(). """Reusable flow control logic for StreamWriter.drain().
This implements the protocol methods pause_writing(), This implements the protocol methods pause_writing(),
resume_reading() and connection_lost(). If the subclass overrides resume_writing() and connection_lost(). If the subclass overrides
these it must call the super methods. these it must call the super methods.
StreamWriter.drain() must wait for _drain_helper() coroutine. StreamWriter.drain() must wait for _drain_helper() coroutine.
@@ -161,7 +129,7 @@ class FlowControlMixin(protocols.Protocol):
else: else:
self._loop = loop self._loop = loop
self._paused = False self._paused = False
self._drain_waiter = None self._drain_waiters = collections.deque()
self._connection_lost = False self._connection_lost = False
def pause_writing(self): def pause_writing(self):
@@ -176,39 +144,37 @@ class FlowControlMixin(protocols.Protocol):
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r resumes writing", self) logger.debug("%r resumes writing", self)
waiter = self._drain_waiter for waiter in self._drain_waiters:
if waiter is not None:
self._drain_waiter = None
if not waiter.done(): if not waiter.done():
waiter.set_result(None) waiter.set_result(None)
def connection_lost(self, exc): def connection_lost(self, exc):
self._connection_lost = True self._connection_lost = True
# Wake up the writer if currently paused. # Wake up the writer(s) if currently paused.
if not self._paused: if not self._paused:
return return
waiter = self._drain_waiter
if waiter is None:
return
self._drain_waiter = None
if waiter.done():
return
if exc is None:
waiter.set_result(None)
else:
waiter.set_exception(exc)
@coroutine for waiter in self._drain_waiters:
def _drain_helper(self): if not waiter.done():
if exc is None:
waiter.set_result(None)
else:
waiter.set_exception(exc)
async def _drain_helper(self):
if self._connection_lost: if self._connection_lost:
raise ConnectionResetError('Connection lost') raise ConnectionResetError('Connection lost')
if not self._paused: if not self._paused:
return return
waiter = self._drain_waiter
assert waiter is None or waiter.cancelled()
waiter = self._loop.create_future() waiter = self._loop.create_future()
self._drain_waiter = waiter self._drain_waiters.append(waiter)
yield from waiter try:
await waiter
finally:
self._drain_waiters.remove(waiter)
def _get_close_waiter(self, stream):
raise NotImplementedError
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol): class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
@@ -220,40 +186,110 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
call inappropriate methods of the protocol.) call inappropriate methods of the protocol.)
""" """
_source_traceback = None
def __init__(self, stream_reader, client_connected_cb=None, loop=None): def __init__(self, stream_reader, client_connected_cb=None, loop=None):
super().__init__(loop=loop) super().__init__(loop=loop)
self._stream_reader = stream_reader if stream_reader is not None:
self._stream_reader_wr = weakref.ref(stream_reader)
self._source_traceback = stream_reader._source_traceback
else:
self._stream_reader_wr = None
if client_connected_cb is not None:
# This is a stream created by the `create_server()` function.
# Keep a strong reference to the reader until a connection
# is established.
self._strong_reader = stream_reader
self._reject_connection = False
self._stream_writer = None self._stream_writer = None
self._task = None
self._transport = None
self._client_connected_cb = client_connected_cb self._client_connected_cb = client_connected_cb
self._over_ssl = False self._over_ssl = False
self._closed = self._loop.create_future()
@property
def _stream_reader(self):
if self._stream_reader_wr is None:
return None
return self._stream_reader_wr()
def _replace_writer(self, writer):
loop = self._loop
transport = writer.transport
self._stream_writer = writer
self._transport = transport
self._over_ssl = transport.get_extra_info('sslcontext') is not None
def connection_made(self, transport): def connection_made(self, transport):
self._stream_reader.set_transport(transport) if self._reject_connection:
context = {
'message': ('An open stream was garbage collected prior to '
'establishing network connection; '
'call "stream.close()" explicitly.')
}
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)
transport.abort()
return
self._transport = transport
reader = self._stream_reader
if reader is not None:
reader.set_transport(transport)
self._over_ssl = transport.get_extra_info('sslcontext') is not None self._over_ssl = transport.get_extra_info('sslcontext') is not None
if self._client_connected_cb is not None: if self._client_connected_cb is not None:
self._stream_writer = StreamWriter(transport, self, self._stream_writer = StreamWriter(transport, self,
self._stream_reader, reader,
self._loop) self._loop)
res = self._client_connected_cb(self._stream_reader, res = self._client_connected_cb(reader,
self._stream_writer) self._stream_writer)
if coroutines.iscoroutine(res): if coroutines.iscoroutine(res):
self._loop.create_task(res) def callback(task):
if task.cancelled():
transport.close()
return
exc = task.exception()
if exc is not None:
self._loop.call_exception_handler({
'message': 'Unhandled exception in client_connected_cb',
'exception': exc,
'transport': transport,
})
transport.close()
self._task = self._loop.create_task(res)
self._task.add_done_callback(callback)
self._strong_reader = None
def connection_lost(self, exc): def connection_lost(self, exc):
if self._stream_reader is not None: reader = self._stream_reader
if reader is not None:
if exc is None: if exc is None:
self._stream_reader.feed_eof() reader.feed_eof()
else: else:
self._stream_reader.set_exception(exc) reader.set_exception(exc)
if not self._closed.done():
if exc is None:
self._closed.set_result(None)
else:
self._closed.set_exception(exc)
super().connection_lost(exc) super().connection_lost(exc)
self._stream_reader = None self._stream_reader_wr = None
self._stream_writer = None self._stream_writer = None
self._task = None
self._transport = None
def data_received(self, data): def data_received(self, data):
self._stream_reader.feed_data(data) reader = self._stream_reader
if reader is not None:
reader.feed_data(data)
def eof_received(self): def eof_received(self):
self._stream_reader.feed_eof() reader = self._stream_reader
if reader is not None:
reader.feed_eof()
if self._over_ssl: if self._over_ssl:
# Prevent a warning in SSLProtocol.eof_received: # Prevent a warning in SSLProtocol.eof_received:
# "returning true from eof_received() # "returning true from eof_received()
@@ -261,6 +297,20 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
return False return False
return True return True
def _get_close_waiter(self, stream):
return self._closed
def __del__(self):
# Prevent reports about unhandled exceptions.
# Better than self._closed._log_traceback = False hack
try:
closed = self._closed
except AttributeError:
pass # failed constructor
else:
if closed.done() and not closed.cancelled():
closed.exception()
class StreamWriter: class StreamWriter:
"""Wraps a Transport. """Wraps a Transport.
@@ -279,12 +329,14 @@ class StreamWriter:
assert reader is None or isinstance(reader, StreamReader) assert reader is None or isinstance(reader, StreamReader)
self._reader = reader self._reader = reader
self._loop = loop self._loop = loop
self._complete_fut = self._loop.create_future()
self._complete_fut.set_result(None)
def __repr__(self): def __repr__(self):
info = [self.__class__.__name__, 'transport=%r' % self._transport] info = [self.__class__.__name__, f'transport={self._transport!r}']
if self._reader is not None: if self._reader is not None:
info.append('reader=%r' % self._reader) info.append(f'reader={self._reader!r}')
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
@property @property
def transport(self): def transport(self):
@@ -305,36 +357,68 @@ class StreamWriter:
def close(self): def close(self):
return self._transport.close() return self._transport.close()
def is_closing(self):
return self._transport.is_closing()
async def wait_closed(self):
await self._protocol._get_close_waiter(self)
def get_extra_info(self, name, default=None): def get_extra_info(self, name, default=None):
return self._transport.get_extra_info(name, default) return self._transport.get_extra_info(name, default)
@coroutine async def drain(self):
def drain(self):
"""Flush the write buffer. """Flush the write buffer.
The intended use is to write The intended use is to write
w.write(data) w.write(data)
yield from w.drain() await w.drain()
""" """
if self._reader is not None: if self._reader is not None:
exc = self._reader.exception() exc = self._reader.exception()
if exc is not None: if exc is not None:
raise exc raise exc
if self._transport is not None: if self._transport.is_closing():
if self._transport.is_closing(): # Wait for protocol.connection_lost() call
# Yield to the event loop so connection_lost() may be # Raise connection closing error if any,
# called. Without this, _drain_helper() would return # ConnectionResetError otherwise
# immediately, and code that calls # Yield to the event loop so connection_lost() may be
# write(...); yield from drain() # called. Without this, _drain_helper() would return
# in a loop would never call connection_lost(), so it # immediately, and code that calls
# would not see an error when the socket is closed. # write(...); await drain()
yield # in a loop would never call connection_lost(), so it
yield from self._protocol._drain_helper() # would not see an error when the socket is closed.
await sleep(0)
await self._protocol._drain_helper()
async def start_tls(self, sslcontext, *,
server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
"""Upgrade an existing stream-based connection to TLS."""
server_side = self._protocol._client_connected_cb is not None
protocol = self._protocol
await self.drain()
new_transport = await self._loop.start_tls( # type: ignore
self._transport, protocol, sslcontext,
server_side=server_side, server_hostname=server_hostname,
ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout)
self._transport = new_transport
protocol._replace_writer(self)
def __del__(self):
if not self._transport.is_closing():
if self._loop.is_closed():
warnings.warn("loop is closed", ResourceWarning)
else:
self.close()
warnings.warn(f"unclosed {self!r}", ResourceWarning)
class StreamReader: class StreamReader:
_source_traceback = None
def __init__(self, limit=_DEFAULT_LIMIT, loop=None): def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
# The line length limit is a security feature; # The line length limit is a security feature;
# it also doubles as half the buffer limit. # it also doubles as half the buffer limit.
@@ -353,24 +437,27 @@ class StreamReader:
self._exception = None self._exception = None
self._transport = None self._transport = None
self._paused = False self._paused = False
if self._loop.get_debug():
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
def __repr__(self): def __repr__(self):
info = ['StreamReader'] info = ['StreamReader']
if self._buffer: if self._buffer:
info.append('%d bytes' % len(self._buffer)) info.append(f'{len(self._buffer)} bytes')
if self._eof: if self._eof:
info.append('eof') info.append('eof')
if self._limit != _DEFAULT_LIMIT: if self._limit != _DEFAULT_LIMIT:
info.append('l=%d' % self._limit) info.append(f'limit={self._limit}')
if self._waiter: if self._waiter:
info.append('w=%r' % self._waiter) info.append(f'waiter={self._waiter!r}')
if self._exception: if self._exception:
info.append('e=%r' % self._exception) info.append(f'exception={self._exception!r}')
if self._transport: if self._transport:
info.append('t=%r' % self._transport) info.append(f'transport={self._transport!r}')
if self._paused: if self._paused:
info.append('paused') info.append('paused')
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
def exception(self): def exception(self):
return self._exception return self._exception
@@ -431,8 +518,7 @@ class StreamReader:
else: else:
self._paused = True self._paused = True
@coroutine async def _wait_for_data(self, func_name):
def _wait_for_data(self, func_name):
"""Wait until feed_data() or feed_eof() is called. """Wait until feed_data() or feed_eof() is called.
If stream was paused, automatically resume it. If stream was paused, automatically resume it.
@@ -442,8 +528,9 @@ class StreamReader:
# would have an unexpected behaviour. It would not possible to know # would have an unexpected behaviour. It would not possible to know
# which coroutine would get the next data. # which coroutine would get the next data.
if self._waiter is not None: if self._waiter is not None:
raise RuntimeError('%s() called while another coroutine is ' raise RuntimeError(
'already waiting for incoming data' % func_name) f'{func_name}() called while another coroutine is '
f'already waiting for incoming data')
assert not self._eof, '_wait_for_data after EOF' assert not self._eof, '_wait_for_data after EOF'
@@ -455,12 +542,11 @@ class StreamReader:
self._waiter = self._loop.create_future() self._waiter = self._loop.create_future()
try: try:
yield from self._waiter await self._waiter
finally: finally:
self._waiter = None self._waiter = None
@coroutine async def readline(self):
def readline(self):
"""Read chunk of data from the stream until newline (b'\n') is found. """Read chunk of data from the stream until newline (b'\n') is found.
On success, return chunk that ends with newline. If only partial On success, return chunk that ends with newline. If only partial
@@ -479,10 +565,10 @@ class StreamReader:
sep = b'\n' sep = b'\n'
seplen = len(sep) seplen = len(sep)
try: try:
line = yield from self.readuntil(sep) line = await self.readuntil(sep)
except IncompleteReadError as e: except exceptions.IncompleteReadError as e:
return e.partial return e.partial
except LimitOverrunError as e: except exceptions.LimitOverrunError as e:
if self._buffer.startswith(sep, e.consumed): if self._buffer.startswith(sep, e.consumed):
del self._buffer[:e.consumed + seplen] del self._buffer[:e.consumed + seplen]
else: else:
@@ -491,8 +577,7 @@ class StreamReader:
raise ValueError(e.args[0]) raise ValueError(e.args[0])
return line return line
@coroutine async def readuntil(self, separator=b'\n'):
def readuntil(self, separator=b'\n'):
"""Read data from the stream until ``separator`` is found. """Read data from the stream until ``separator`` is found.
On success, the data and separator will be removed from the On success, the data and separator will be removed from the
@@ -558,7 +643,7 @@ class StreamReader:
# see upper comment for explanation. # see upper comment for explanation.
offset = buflen + 1 - seplen offset = buflen + 1 - seplen
if offset > self._limit: if offset > self._limit:
raise LimitOverrunError( raise exceptions.LimitOverrunError(
'Separator is not found, and chunk exceed the limit', 'Separator is not found, and chunk exceed the limit',
offset) offset)
@@ -569,13 +654,13 @@ class StreamReader:
if self._eof: if self._eof:
chunk = bytes(self._buffer) chunk = bytes(self._buffer)
self._buffer.clear() self._buffer.clear()
raise IncompleteReadError(chunk, None) raise exceptions.IncompleteReadError(chunk, None)
# _wait_for_data() will resume reading if stream was paused. # _wait_for_data() will resume reading if stream was paused.
yield from self._wait_for_data('readuntil') await self._wait_for_data('readuntil')
if isep > self._limit: if isep > self._limit:
raise LimitOverrunError( raise exceptions.LimitOverrunError(
'Separator is found, but chunk is longer than limit', isep) 'Separator is found, but chunk is longer than limit', isep)
chunk = self._buffer[:isep + seplen] chunk = self._buffer[:isep + seplen]
@@ -583,20 +668,20 @@ class StreamReader:
self._maybe_resume_transport() self._maybe_resume_transport()
return bytes(chunk) return bytes(chunk)
@coroutine async def read(self, n=-1):
def read(self, n=-1):
"""Read up to `n` bytes from the stream. """Read up to `n` bytes from the stream.
If n is not provided, or set to -1, read until EOF and return all read If `n` is not provided or set to -1,
bytes. If the EOF was received and the internal buffer is empty, return read until EOF, then return all read bytes.
an empty bytes object. If EOF was received and the internal buffer is empty,
return an empty bytes object.
If n is zero, return empty bytes object immediately. If `n` is 0, return an empty bytes object immediately.
If n is positive, this function try to read `n` bytes, and may return If `n` is positive, return at most `n` available bytes
less or equal bytes than requested, but at least one byte. If EOF was as soon as at least 1 byte is available in the internal buffer.
received before any byte is read, this function returns empty byte If EOF is received before any byte is read, return an empty
object. bytes object.
Returned value is not limited with limit, configured at stream Returned value is not limited with limit, configured at stream
creation. creation.
@@ -618,24 +703,23 @@ class StreamReader:
# bytes. So just call self.read(self._limit) until EOF. # bytes. So just call self.read(self._limit) until EOF.
blocks = [] blocks = []
while True: while True:
block = yield from self.read(self._limit) block = await self.read(self._limit)
if not block: if not block:
break break
blocks.append(block) blocks.append(block)
return b''.join(blocks) return b''.join(blocks)
if not self._buffer and not self._eof: if not self._buffer and not self._eof:
yield from self._wait_for_data('read') await self._wait_for_data('read')
# This will work right even if buffer is less than n bytes # This will work right even if buffer is less than n bytes
data = bytes(self._buffer[:n]) data = bytes(memoryview(self._buffer)[:n])
del self._buffer[:n] del self._buffer[:n]
self._maybe_resume_transport() self._maybe_resume_transport()
return data return data
@coroutine async def readexactly(self, n):
def readexactly(self, n):
"""Read exactly `n` bytes. """Read exactly `n` bytes.
Raise an IncompleteReadError if EOF is reached before `n` bytes can be Raise an IncompleteReadError if EOF is reached before `n` bytes can be
@@ -663,33 +747,24 @@ class StreamReader:
if self._eof: if self._eof:
incomplete = bytes(self._buffer) incomplete = bytes(self._buffer)
self._buffer.clear() self._buffer.clear()
raise IncompleteReadError(incomplete, n) raise exceptions.IncompleteReadError(incomplete, n)
yield from self._wait_for_data('readexactly') await self._wait_for_data('readexactly')
if len(self._buffer) == n: if len(self._buffer) == n:
data = bytes(self._buffer) data = bytes(self._buffer)
self._buffer.clear() self._buffer.clear()
else: else:
data = bytes(self._buffer[:n]) data = bytes(memoryview(self._buffer)[:n])
del self._buffer[:n] del self._buffer[:n]
self._maybe_resume_transport() self._maybe_resume_transport()
return data return data
if compat.PY35: def __aiter__(self):
@coroutine return self
def __aiter__(self):
return self
@coroutine async def __anext__(self):
def __anext__(self): val = await self.readline()
val = yield from self.readline() if val == b'':
if val == b'': raise StopAsyncIteration
raise StopAsyncIteration return val
return val
if compat.PY352:
# In Python 3.5.2 and greater, __aiter__ should return
# the asynchronous iterator directly.
def __aiter__(self):
return self

View File

@@ -1,4 +1,4 @@
__all__ = ['create_subprocess_exec', 'create_subprocess_shell'] __all__ = 'create_subprocess_exec', 'create_subprocess_shell'
import subprocess import subprocess
@@ -6,7 +6,6 @@ from . import events
from . import protocols from . import protocols
from . import streams from . import streams
from . import tasks from . import tasks
from .coroutines import coroutine
from .log import logger from .log import logger
@@ -24,16 +23,19 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
self._limit = limit self._limit = limit
self.stdin = self.stdout = self.stderr = None self.stdin = self.stdout = self.stderr = None
self._transport = None self._transport = None
self._process_exited = False
self._pipe_fds = []
self._stdin_closed = self._loop.create_future()
def __repr__(self): def __repr__(self):
info = [self.__class__.__name__] info = [self.__class__.__name__]
if self.stdin is not None: if self.stdin is not None:
info.append('stdin=%r' % self.stdin) info.append(f'stdin={self.stdin!r}')
if self.stdout is not None: if self.stdout is not None:
info.append('stdout=%r' % self.stdout) info.append(f'stdout={self.stdout!r}')
if self.stderr is not None: if self.stderr is not None:
info.append('stderr=%r' % self.stderr) info.append(f'stderr={self.stderr!r}')
return '<%s>' % ' '.join(info) return '<{}>'.format(' '.join(info))
def connection_made(self, transport): def connection_made(self, transport):
self._transport = transport self._transport = transport
@@ -43,12 +45,14 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
self.stdout = streams.StreamReader(limit=self._limit, self.stdout = streams.StreamReader(limit=self._limit,
loop=self._loop) loop=self._loop)
self.stdout.set_transport(stdout_transport) self.stdout.set_transport(stdout_transport)
self._pipe_fds.append(1)
stderr_transport = transport.get_pipe_transport(2) stderr_transport = transport.get_pipe_transport(2)
if stderr_transport is not None: if stderr_transport is not None:
self.stderr = streams.StreamReader(limit=self._limit, self.stderr = streams.StreamReader(limit=self._limit,
loop=self._loop) loop=self._loop)
self.stderr.set_transport(stderr_transport) self.stderr.set_transport(stderr_transport)
self._pipe_fds.append(2)
stdin_transport = transport.get_pipe_transport(0) stdin_transport = transport.get_pipe_transport(0)
if stdin_transport is not None: if stdin_transport is not None:
@@ -73,6 +77,13 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
if pipe is not None: if pipe is not None:
pipe.close() pipe.close()
self.connection_lost(exc) self.connection_lost(exc)
if exc is None:
self._stdin_closed.set_result(None)
else:
self._stdin_closed.set_exception(exc)
# Since calling `wait_closed()` is not mandatory,
# we shouldn't log the traceback if this is not awaited.
self._stdin_closed._log_traceback = False
return return
if fd == 1: if fd == 1:
reader = self.stdout reader = self.stdout
@@ -80,15 +91,28 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
reader = self.stderr reader = self.stderr
else: else:
reader = None reader = None
if reader != None: if reader is not None:
if exc is None: if exc is None:
reader.feed_eof() reader.feed_eof()
else: else:
reader.set_exception(exc) reader.set_exception(exc)
if fd in self._pipe_fds:
self._pipe_fds.remove(fd)
self._maybe_close_transport()
def process_exited(self): def process_exited(self):
self._transport.close() self._process_exited = True
self._transport = None self._maybe_close_transport()
def _maybe_close_transport(self):
if len(self._pipe_fds) == 0 and self._process_exited:
self._transport.close()
self._transport = None
def _get_close_waiter(self, stream):
if stream is self.stdin:
return self._stdin_closed
class Process: class Process:
@@ -102,18 +126,15 @@ class Process:
self.pid = transport.get_pid() self.pid = transport.get_pid()
def __repr__(self): def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.pid) return f'<{self.__class__.__name__} {self.pid}>'
@property @property
def returncode(self): def returncode(self):
return self._transport.get_returncode() return self._transport.get_returncode()
@coroutine async def wait(self):
def wait(self): """Wait until the process exit and return the process return code."""
"""Wait until the process exit and return the process return code. return await self._transport._wait()
This method is a coroutine."""
return (yield from self._transport._wait())
def send_signal(self, signal): def send_signal(self, signal):
self._transport.send_signal(signal) self._transport.send_signal(signal)
@@ -124,17 +145,19 @@ class Process:
def kill(self): def kill(self):
self._transport.kill() self._transport.kill()
@coroutine async def _feed_stdin(self, input):
def _feed_stdin(self, input):
debug = self._loop.get_debug() debug = self._loop.get_debug()
self.stdin.write(input)
if debug:
logger.debug('%r communicate: feed stdin (%s bytes)',
self, len(input))
try: try:
yield from self.stdin.drain() if input is not None:
self.stdin.write(input)
if debug:
logger.debug(
'%r communicate: feed stdin (%s bytes)', self, len(input))
await self.stdin.drain()
except (BrokenPipeError, ConnectionResetError) as exc: except (BrokenPipeError, ConnectionResetError) as exc:
# communicate() ignores BrokenPipeError and ConnectionResetError # communicate() ignores BrokenPipeError and ConnectionResetError.
# write() and drain() can raise these exceptions.
if debug: if debug:
logger.debug('%r communicate: stdin got %r', self, exc) logger.debug('%r communicate: stdin got %r', self, exc)
@@ -142,12 +165,10 @@ class Process:
logger.debug('%r communicate: close stdin', self) logger.debug('%r communicate: close stdin', self)
self.stdin.close() self.stdin.close()
@coroutine async def _noop(self):
def _noop(self):
return None return None
@coroutine async def _read_stream(self, fd):
def _read_stream(self, fd):
transport = self._transport.get_pipe_transport(fd) transport = self._transport.get_pipe_transport(fd)
if fd == 2: if fd == 2:
stream = self.stderr stream = self.stderr
@@ -157,16 +178,15 @@ class Process:
if self._loop.get_debug(): if self._loop.get_debug():
name = 'stdout' if fd == 1 else 'stderr' name = 'stdout' if fd == 1 else 'stderr'
logger.debug('%r communicate: read %s', self, name) logger.debug('%r communicate: read %s', self, name)
output = yield from stream.read() output = await stream.read()
if self._loop.get_debug(): if self._loop.get_debug():
name = 'stdout' if fd == 1 else 'stderr' name = 'stdout' if fd == 1 else 'stderr'
logger.debug('%r communicate: close %s', self, name) logger.debug('%r communicate: close %s', self, name)
transport.close() transport.close()
return output return output
@coroutine async def communicate(self, input=None):
def communicate(self, input=None): if self.stdin is not None:
if input is not None:
stdin = self._feed_stdin(input) stdin = self._feed_stdin(input)
else: else:
stdin = self._noop() stdin = self._noop()
@@ -178,36 +198,32 @@ class Process:
stderr = self._read_stream(2) stderr = self._read_stream(2)
else: else:
stderr = self._noop() stderr = self._noop()
stdin, stdout, stderr = yield from tasks.gather(stdin, stdout, stderr, stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr)
loop=self._loop) await self.wait()
yield from self.wait()
return (stdout, stderr) return (stdout, stderr)
@coroutine async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, limit=streams._DEFAULT_LIMIT, **kwds):
loop=None, limit=streams._DEFAULT_LIMIT, **kwds): loop = events.get_running_loop()
if loop is None:
loop = events.get_event_loop()
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
loop=loop) loop=loop)
transport, protocol = yield from loop.subprocess_shell( transport, protocol = await loop.subprocess_shell(
protocol_factory, protocol_factory,
cmd, stdin=stdin, stdout=stdout, cmd, stdin=stdin, stdout=stdout,
stderr=stderr, **kwds) stderr=stderr, **kwds)
return Process(transport, protocol, loop) return Process(transport, protocol, loop)
@coroutine
def create_subprocess_exec(program, *args, stdin=None, stdout=None, async def create_subprocess_exec(program, *args, stdin=None, stdout=None,
stderr=None, loop=None, stderr=None, limit=streams._DEFAULT_LIMIT,
limit=streams._DEFAULT_LIMIT, **kwds): **kwds):
if loop is None: loop = events.get_running_loop()
loop = events.get_event_loop()
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
loop=loop) loop=loop)
transport, protocol = yield from loop.subprocess_exec( transport, protocol = await loop.subprocess_exec(
protocol_factory, protocol_factory,
program, *args, program, *args,
stdin=stdin, stdout=stdout, stdin=stdin, stdout=stdout,
stderr=stderr, **kwds) stderr=stderr, **kwds)
return Process(transport, protocol, loop) return Process(transport, protocol, loop)

240
Lib/asyncio/taskgroups.py vendored Normal file
View File

@@ -0,0 +1,240 @@
# Adapted with permission from the EdgeDB project;
# license: PSFL.
__all__ = ("TaskGroup",)
from . import events
from . import exceptions
from . import tasks
class TaskGroup:
"""Asynchronous context manager for managing groups of tasks.
Example use:
async with asyncio.TaskGroup() as group:
task1 = group.create_task(some_coroutine(...))
task2 = group.create_task(other_coroutine(...))
print("Both tasks have completed now.")
All tasks are awaited when the context manager exits.
Any exceptions other than `asyncio.CancelledError` raised within
a task will cancel all remaining tasks and wait for them to exit.
The exceptions are then combined and raised as an `ExceptionGroup`.
"""
def __init__(self):
self._entered = False
self._exiting = False
self._aborting = False
self._loop = None
self._parent_task = None
self._parent_cancel_requested = False
self._tasks = set()
self._errors = []
self._base_error = None
self._on_completed_fut = None
def __repr__(self):
info = ['']
if self._tasks:
info.append(f'tasks={len(self._tasks)}')
if self._errors:
info.append(f'errors={len(self._errors)}')
if self._aborting:
info.append('cancelling')
elif self._entered:
info.append('entered')
info_str = ' '.join(info)
return f'<TaskGroup{info_str}>'
async def __aenter__(self):
if self._entered:
raise RuntimeError(
f"TaskGroup {self!r} has already been entered")
if self._loop is None:
self._loop = events.get_running_loop()
self._parent_task = tasks.current_task(self._loop)
if self._parent_task is None:
raise RuntimeError(
f'TaskGroup {self!r} cannot determine the parent task')
self._entered = True
return self
async def __aexit__(self, et, exc, tb):
self._exiting = True
if (exc is not None and
self._is_base_error(exc) and
self._base_error is None):
self._base_error = exc
propagate_cancellation_error = \
exc if et is exceptions.CancelledError else None
if self._parent_cancel_requested:
# If this flag is set we *must* call uncancel().
if self._parent_task.uncancel() == 0:
# If there are no pending cancellations left,
# don't propagate CancelledError.
propagate_cancellation_error = None
if et is not None:
if not self._aborting:
# Our parent task is being cancelled:
#
# async with TaskGroup() as g:
# g.create_task(...)
# await ... # <- CancelledError
#
# or there's an exception in "async with":
#
# async with TaskGroup() as g:
# g.create_task(...)
# 1 / 0
#
self._abort()
# We use while-loop here because "self._on_completed_fut"
# can be cancelled multiple times if our parent task
# is being cancelled repeatedly (or even once, when
# our own cancellation is already in progress)
while self._tasks:
if self._on_completed_fut is None:
self._on_completed_fut = self._loop.create_future()
try:
await self._on_completed_fut
except exceptions.CancelledError as ex:
if not self._aborting:
# Our parent task is being cancelled:
#
# async def wrapper():
# async with TaskGroup() as g:
# g.create_task(foo)
#
# "wrapper" is being cancelled while "foo" is
# still running.
propagate_cancellation_error = ex
self._abort()
self._on_completed_fut = None
assert not self._tasks
if self._base_error is not None:
raise self._base_error
# Propagate CancelledError if there is one, except if there
# are other errors -- those have priority.
if propagate_cancellation_error and not self._errors:
raise propagate_cancellation_error
if et is not None and et is not exceptions.CancelledError:
self._errors.append(exc)
if self._errors:
# Exceptions are heavy objects that can have object
# cycles (bad for GC); let's not keep a reference to
# a bunch of them.
try:
me = BaseExceptionGroup('unhandled errors in a TaskGroup', self._errors)
raise me from None
finally:
self._errors = None
def create_task(self, coro, *, name=None, context=None):
"""Create a new task in this group and return it.
Similar to `asyncio.create_task`.
"""
if not self._entered:
raise RuntimeError(f"TaskGroup {self!r} has not been entered")
if self._exiting and not self._tasks:
raise RuntimeError(f"TaskGroup {self!r} is finished")
if self._aborting:
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
if context is None:
task = self._loop.create_task(coro)
else:
task = self._loop.create_task(coro, context=context)
tasks._set_task_name(task, name)
# optimization: Immediately call the done callback if the task is
# already done (e.g. if the coro was able to complete eagerly),
# and skip scheduling a done callback
if task.done():
self._on_task_done(task)
else:
self._tasks.add(task)
task.add_done_callback(self._on_task_done)
return task
# Since Python 3.8 Tasks propagate all exceptions correctly,
# except for KeyboardInterrupt and SystemExit which are
# still considered special.
def _is_base_error(self, exc: BaseException) -> bool:
assert isinstance(exc, BaseException)
return isinstance(exc, (SystemExit, KeyboardInterrupt))
def _abort(self):
self._aborting = True
for t in self._tasks:
if not t.done():
t.cancel()
def _on_task_done(self, task):
self._tasks.discard(task)
if self._on_completed_fut is not None and not self._tasks:
if not self._on_completed_fut.done():
self._on_completed_fut.set_result(True)
if task.cancelled():
return
exc = task.exception()
if exc is None:
return
self._errors.append(exc)
if self._is_base_error(exc) and self._base_error is None:
self._base_error = exc
if self._parent_task.done():
# Not sure if this case is possible, but we want to handle
# it anyways.
self._loop.call_exception_handler({
'message': f'Task {task!r} has errored out but its parent '
f'task {self._parent_task} is already completed',
'exception': exc,
'task': task,
})
return
if not self._aborting and not self._parent_cancel_requested:
# If parent task *is not* being cancelled, it means that we want
# to manually cancel it to abort whatever is being run right now
# in the TaskGroup. But we want to mark parent task as
# "not cancelled" later in __aexit__. Example situation that
# we need to handle:
#
# async def foo():
# try:
# async with TaskGroup() as g:
# g.create_task(crash_soon())
# await something # <- this needs to be canceled
# # by the TaskGroup, e.g.
# # foo() needs to be cancelled
# except Exception:
# # Ignore any exceptions raised in the TaskGroup
# pass
# await something_else # this line has to be called
# # after TaskGroup is finished.
self._abort()
self._parent_cancel_requested = True
self._parent_task.cancel()

853
Lib/asyncio/tasks.py vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,503 +0,0 @@
"""Utilities shared by tests."""
import collections
import contextlib
import io
import logging
import os
import re
import socket
import socketserver
import sys
import tempfile
import threading
import time
import unittest
import weakref
from unittest import mock
from http.server import HTTPServer
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
try:
import ssl
except ImportError: # pragma: no cover
ssl = None
from . import base_events
from . import compat
from . import events
from . import futures
from . import selectors
from . import tasks
from .coroutines import coroutine
from .log import logger
if sys.platform == 'win32': # pragma: no cover
from .windows_utils import socketpair
else:
from socket import socketpair # pragma: no cover
def dummy_ssl_context():
if ssl is None:
return None
else:
return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
def run_briefly(loop):
@coroutine
def once():
pass
gen = once()
t = loop.create_task(gen)
# Don't log a warning if the task is not done after run_until_complete().
# It occurs if the loop is stopped or if a task raises a BaseException.
t._log_destroy_pending = False
try:
loop.run_until_complete(t)
finally:
gen.close()
def run_until(loop, pred, timeout=30):
deadline = time.time() + timeout
while not pred():
if timeout is not None:
timeout = deadline - time.time()
if timeout <= 0:
raise futures.TimeoutError()
loop.run_until_complete(tasks.sleep(0.001, loop=loop))
def run_once(loop):
"""Legacy API to run once through the event loop.
This is the recommended pattern for test code. It will poll the
selector once and run all callbacks scheduled in response to I/O
events.
"""
loop.call_soon(loop.stop)
loop.run_forever()
class SilentWSGIRequestHandler(WSGIRequestHandler):
def get_stderr(self):
return io.StringIO()
def log_message(self, format, *args):
pass
class SilentWSGIServer(WSGIServer):
request_timeout = 2
def get_request(self):
request, client_addr = super().get_request()
request.settimeout(self.request_timeout)
return request, client_addr
def handle_error(self, request, client_address):
pass
class SSLWSGIServerMixin:
def finish_request(self, request, client_address):
# The relative location of our test directory (which
# contains the ssl key and certificate files) differs
# between the stdlib and stand-alone asyncio.
# Prefer our own if we can find it.
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
if not os.path.isdir(here):
here = os.path.join(os.path.dirname(os.__file__),
'test', 'test_asyncio')
keyfile = os.path.join(here, 'ssl_key.pem')
certfile = os.path.join(here, 'ssl_cert.pem')
context = ssl.SSLContext()
context.load_cert_chain(certfile, keyfile)
ssock = context.wrap_socket(request, server_side=True)
try:
self.RequestHandlerClass(ssock, client_address, self)
ssock.close()
except OSError:
# maybe socket has been closed by peer
pass
class SSLWSGIServer(SSLWSGIServerMixin, SilentWSGIServer):
pass
def _run_test_server(*, address, use_ssl=False, server_cls, server_ssl_cls):
def app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return [b'Test message']
# Run the test WSGI server in a separate thread in order not to
# interfere with event handling in the main thread
server_class = server_ssl_cls if use_ssl else server_cls
httpd = server_class(address, SilentWSGIRequestHandler)
httpd.set_app(app)
httpd.address = httpd.server_address
server_thread = threading.Thread(
target=lambda: httpd.serve_forever(poll_interval=0.05))
server_thread.start()
try:
yield httpd
finally:
httpd.shutdown()
httpd.server_close()
server_thread.join()
if hasattr(socket, 'AF_UNIX'):
class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer):
def server_bind(self):
socketserver.UnixStreamServer.server_bind(self)
self.server_name = '127.0.0.1'
self.server_port = 80
class UnixWSGIServer(UnixHTTPServer, WSGIServer):
request_timeout = 2
def server_bind(self):
UnixHTTPServer.server_bind(self)
self.setup_environ()
def get_request(self):
request, client_addr = super().get_request()
request.settimeout(self.request_timeout)
# Code in the stdlib expects that get_request
# will return a socket and a tuple (host, port).
# However, this isn't true for UNIX sockets,
# as the second return value will be a path;
# hence we return some fake data sufficient
# to get the tests going
return request, ('127.0.0.1', '')
class SilentUnixWSGIServer(UnixWSGIServer):
def handle_error(self, request, client_address):
pass
class UnixSSLWSGIServer(SSLWSGIServerMixin, SilentUnixWSGIServer):
pass
def gen_unix_socket_path():
with tempfile.NamedTemporaryFile() as file:
return file.name
@contextlib.contextmanager
def unix_socket_path():
path = gen_unix_socket_path()
try:
yield path
finally:
try:
os.unlink(path)
except OSError:
pass
@contextlib.contextmanager
def run_test_unix_server(*, use_ssl=False):
with unix_socket_path() as path:
yield from _run_test_server(address=path, use_ssl=use_ssl,
server_cls=SilentUnixWSGIServer,
server_ssl_cls=UnixSSLWSGIServer)
@contextlib.contextmanager
def run_test_server(*, host='127.0.0.1', port=0, use_ssl=False):
yield from _run_test_server(address=(host, port), use_ssl=use_ssl,
server_cls=SilentWSGIServer,
server_ssl_cls=SSLWSGIServer)
def make_test_protocol(base):
dct = {}
for name in dir(base):
if name.startswith('__') and name.endswith('__'):
# skip magic names
continue
dct[name] = MockCallback(return_value=None)
return type('TestProtocol', (base,) + base.__bases__, dct)()
class TestSelector(selectors.BaseSelector):
def __init__(self):
self.keys = {}
def register(self, fileobj, events, data=None):
key = selectors.SelectorKey(fileobj, 0, events, data)
self.keys[fileobj] = key
return key
def unregister(self, fileobj):
return self.keys.pop(fileobj)
def select(self, timeout):
return []
def get_map(self):
return self.keys
class TestLoop(base_events.BaseEventLoop):
"""Loop for unittests.
It manages self time directly.
If something scheduled to be executed later then
on next loop iteration after all ready handlers done
generator passed to __init__ is calling.
Generator should be like this:
def gen():
...
when = yield ...
... = yield time_advance
Value returned by yield is absolute time of next scheduled handler.
Value passed to yield is time advance to move loop's time forward.
"""
def __init__(self, gen=None):
super().__init__()
if gen is None:
def gen():
yield
self._check_on_close = False
else:
self._check_on_close = True
self._gen = gen()
next(self._gen)
self._time = 0
self._clock_resolution = 1e-9
self._timers = []
self._selector = TestSelector()
self.readers = {}
self.writers = {}
self.reset_counters()
self._transports = weakref.WeakValueDictionary()
def time(self):
return self._time
def advance_time(self, advance):
"""Move test time forward."""
if advance:
self._time += advance
def close(self):
super().close()
if self._check_on_close:
try:
self._gen.send(0)
except StopIteration:
pass
else: # pragma: no cover
raise AssertionError("Time generator is not finished")
def _add_reader(self, fd, callback, *args):
self.readers[fd] = events.Handle(callback, args, self)
def _remove_reader(self, fd):
self.remove_reader_count[fd] += 1
if fd in self.readers:
del self.readers[fd]
return True
else:
return False
def assert_reader(self, fd, callback, *args):
assert fd in self.readers, 'fd {} is not registered'.format(fd)
handle = self.readers[fd]
assert handle._callback == callback, '{!r} != {!r}'.format(
handle._callback, callback)
assert handle._args == args, '{!r} != {!r}'.format(
handle._args, args)
def _add_writer(self, fd, callback, *args):
self.writers[fd] = events.Handle(callback, args, self)
def _remove_writer(self, fd):
self.remove_writer_count[fd] += 1
if fd in self.writers:
del self.writers[fd]
return True
else:
return False
def assert_writer(self, fd, callback, *args):
assert fd in self.writers, 'fd {} is not registered'.format(fd)
handle = self.writers[fd]
assert handle._callback == callback, '{!r} != {!r}'.format(
handle._callback, callback)
assert handle._args == args, '{!r} != {!r}'.format(
handle._args, args)
def _ensure_fd_no_transport(self, fd):
try:
transport = self._transports[fd]
except KeyError:
pass
else:
raise RuntimeError(
'File descriptor {!r} is used by transport {!r}'.format(
fd, transport))
def add_reader(self, fd, callback, *args):
"""Add a reader callback."""
self._ensure_fd_no_transport(fd)
return self._add_reader(fd, callback, *args)
def remove_reader(self, fd):
"""Remove a reader callback."""
self._ensure_fd_no_transport(fd)
return self._remove_reader(fd)
def add_writer(self, fd, callback, *args):
"""Add a writer callback.."""
self._ensure_fd_no_transport(fd)
return self._add_writer(fd, callback, *args)
def remove_writer(self, fd):
"""Remove a writer callback."""
self._ensure_fd_no_transport(fd)
return self._remove_writer(fd)
def reset_counters(self):
self.remove_reader_count = collections.defaultdict(int)
self.remove_writer_count = collections.defaultdict(int)
def _run_once(self):
super()._run_once()
for when in self._timers:
advance = self._gen.send(when)
self.advance_time(advance)
self._timers = []
def call_at(self, when, callback, *args):
self._timers.append(when)
return super().call_at(when, callback, *args)
def _process_events(self, event_list):
return
def _write_to_self(self):
pass
def MockCallback(**kwargs):
return mock.Mock(spec=['__call__'], **kwargs)
class MockPattern(str):
"""A regex based str with a fuzzy __eq__.
Use this helper with 'mock.assert_called_with', or anywhere
where a regex comparison between strings is needed.
For instance:
mock_call.assert_called_with(MockPattern('spam.*ham'))
"""
def __eq__(self, other):
return bool(re.search(str(self), other, re.S))
def get_function_source(func):
source = events._get_function_source(func)
if source is None:
raise ValueError("unable to get the source of %r" % (func,))
return source
class TestCase(unittest.TestCase):
def set_event_loop(self, loop, *, cleanup=True):
assert loop is not None
# ensure that the event loop is passed explicitly in asyncio
events.set_event_loop(None)
if cleanup:
self.addCleanup(loop.close)
def new_test_loop(self, gen=None):
loop = TestLoop(gen)
self.set_event_loop(loop)
return loop
def setUp(self):
self._get_running_loop = events._get_running_loop
events._get_running_loop = lambda: None
def tearDown(self):
events._get_running_loop = self._get_running_loop
events.set_event_loop(None)
# Detect CPython bug #23353: ensure that yield/yield-from is not used
# in an except block of a generator
self.assertEqual(sys.exc_info(), (None, None, None))
if not compat.PY34:
# Python 3.3 compatibility
def subTest(self, *args, **kwargs):
class EmptyCM:
def __enter__(self):
pass
def __exit__(self, *exc):
pass
return EmptyCM()
@contextlib.contextmanager
def disable_logger():
"""Context manager to disable asyncio logger.
For example, it can be used to ignore warnings in debug mode.
"""
old_level = logger.level
try:
logger.setLevel(logging.CRITICAL+1)
yield
finally:
logger.setLevel(old_level)
def mock_nonblocking_socket(proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM,
family=socket.AF_INET):
"""Create a mock of a non-blocking socket."""
sock = mock.MagicMock(socket.socket)
sock.proto = proto
sock.type = type
sock.family = family
sock.gettimeout.return_value = 0.0
return sock
def force_legacy_ssl_support():
return mock.patch('asyncio.sslproto._is_sslproto_available',
return_value=False)

25
Lib/asyncio/threads.py vendored Normal file
View File

@@ -0,0 +1,25 @@
"""High-level support for working with threads in asyncio"""
import functools
import contextvars
from . import events
__all__ = "to_thread",
async def to_thread(func, /, *args, **kwargs):
"""Asynchronously run function *func* in a separate thread.
Any *args and **kwargs supplied for this function are directly passed
to *func*. Also, the current :class:`contextvars.Context` is propagated,
allowing context variables from the main thread to be accessed in the
separate thread.
Return a coroutine that can be awaited to get the eventual result of *func*.
"""
loop = events.get_running_loop()
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
return await loop.run_in_executor(None, func_call)

168
Lib/asyncio/timeouts.py vendored Normal file
View File

@@ -0,0 +1,168 @@
import enum
from types import TracebackType
from typing import final, Optional, Type
from . import events
from . import exceptions
from . import tasks
__all__ = (
"Timeout",
"timeout",
"timeout_at",
)
class _State(enum.Enum):
CREATED = "created"
ENTERED = "active"
EXPIRING = "expiring"
EXPIRED = "expired"
EXITED = "finished"
@final
class Timeout:
"""Asynchronous context manager for cancelling overdue coroutines.
Use `timeout()` or `timeout_at()` rather than instantiating this class directly.
"""
def __init__(self, when: Optional[float]) -> None:
"""Schedule a timeout that will trigger at a given loop time.
- If `when` is `None`, the timeout will never trigger.
- If `when < loop.time()`, the timeout will trigger on the next
iteration of the event loop.
"""
self._state = _State.CREATED
self._timeout_handler: Optional[events.TimerHandle] = None
self._task: Optional[tasks.Task] = None
self._when = when
def when(self) -> Optional[float]:
"""Return the current deadline."""
return self._when
def reschedule(self, when: Optional[float]) -> None:
"""Reschedule the timeout."""
if self._state is not _State.ENTERED:
if self._state is _State.CREATED:
raise RuntimeError("Timeout has not been entered")
raise RuntimeError(
f"Cannot change state of {self._state.value} Timeout",
)
self._when = when
if self._timeout_handler is not None:
self._timeout_handler.cancel()
if when is None:
self._timeout_handler = None
else:
loop = events.get_running_loop()
if when <= loop.time():
self._timeout_handler = loop.call_soon(self._on_timeout)
else:
self._timeout_handler = loop.call_at(when, self._on_timeout)
def expired(self) -> bool:
"""Is timeout expired during execution?"""
return self._state in (_State.EXPIRING, _State.EXPIRED)
def __repr__(self) -> str:
info = ['']
if self._state is _State.ENTERED:
when = round(self._when, 3) if self._when is not None else None
info.append(f"when={when}")
info_str = ' '.join(info)
return f"<Timeout [{self._state.value}]{info_str}>"
async def __aenter__(self) -> "Timeout":
if self._state is not _State.CREATED:
raise RuntimeError("Timeout has already been entered")
task = tasks.current_task()
if task is None:
raise RuntimeError("Timeout should be used inside a task")
self._state = _State.ENTERED
self._task = task
self._cancelling = self._task.cancelling()
self.reschedule(self._when)
return self
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
assert self._state in (_State.ENTERED, _State.EXPIRING)
if self._timeout_handler is not None:
self._timeout_handler.cancel()
self._timeout_handler = None
if self._state is _State.EXPIRING:
self._state = _State.EXPIRED
if self._task.uncancel() <= self._cancelling and exc_type is exceptions.CancelledError:
# Since there are no new cancel requests, we're
# handling this.
raise TimeoutError from exc_val
elif self._state is _State.ENTERED:
self._state = _State.EXITED
return None
def _on_timeout(self) -> None:
assert self._state is _State.ENTERED
self._task.cancel()
self._state = _State.EXPIRING
# drop the reference early
self._timeout_handler = None
def timeout(delay: Optional[float]) -> Timeout:
"""Timeout async context manager.
Useful in cases when you want to apply timeout logic around block
of code or in cases when asyncio.wait_for is not suitable. For example:
>>> async with asyncio.timeout(10): # 10 seconds timeout
... await long_running_task()
delay - value in seconds or None to disable timeout logic
long_running_task() is interrupted by raising asyncio.CancelledError,
the top-most affected timeout() context manager converts CancelledError
into TimeoutError.
"""
loop = events.get_running_loop()
return Timeout(loop.time() + delay if delay is not None else None)
def timeout_at(when: Optional[float]) -> Timeout:
"""Schedule the timeout at absolute time.
Like timeout() but argument gives absolute time in the same clock system
as loop.time().
Please note: it is not POSIX time but a time with
undefined starting base, e.g. the time of the system power on.
>>> async with asyncio.timeout_at(loop.time() + 10):
... await long_running_task()
when - a deadline when timeout occurs or None to disable timeout logic
long_running_task() is interrupted by raising asyncio.CancelledError,
the top-most affected timeout() context manager converts CancelledError
into TimeoutError.
"""
return Timeout(when)

View File

@@ -1,15 +1,16 @@
"""Abstract Transport class.""" """Abstract Transport class."""
from asyncio import compat __all__ = (
'BaseTransport', 'ReadTransport', 'WriteTransport',
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport', 'Transport', 'DatagramTransport', 'SubprocessTransport',
'Transport', 'DatagramTransport', 'SubprocessTransport', )
]
class BaseTransport: class BaseTransport:
"""Base class for transports.""" """Base class for transports."""
__slots__ = ('_extra',)
def __init__(self, extra=None): def __init__(self, extra=None):
if extra is None: if extra is None:
extra = {} extra = {}
@@ -28,8 +29,8 @@ class BaseTransport:
Buffered data will be flushed asynchronously. No more data Buffered data will be flushed asynchronously. No more data
will be received. After all buffered data is flushed, the will be received. After all buffered data is flushed, the
protocol's connection_lost() method will (eventually) called protocol's connection_lost() method will (eventually) be
with None as its argument. called with None as its argument.
""" """
raise NotImplementedError raise NotImplementedError
@@ -45,6 +46,12 @@ class BaseTransport:
class ReadTransport(BaseTransport): class ReadTransport(BaseTransport):
"""Interface for read-only transports.""" """Interface for read-only transports."""
__slots__ = ()
def is_reading(self):
"""Return True if the transport is receiving."""
raise NotImplementedError
def pause_reading(self): def pause_reading(self):
"""Pause the receiving end. """Pause the receiving end.
@@ -65,6 +72,8 @@ class ReadTransport(BaseTransport):
class WriteTransport(BaseTransport): class WriteTransport(BaseTransport):
"""Interface for write-only transports.""" """Interface for write-only transports."""
__slots__ = ()
def set_write_buffer_limits(self, high=None, low=None): def set_write_buffer_limits(self, high=None, low=None):
"""Set the high- and low-water limits for write flow control. """Set the high- and low-water limits for write flow control.
@@ -90,6 +99,12 @@ class WriteTransport(BaseTransport):
"""Return the current size of the write buffer.""" """Return the current size of the write buffer."""
raise NotImplementedError raise NotImplementedError
def get_write_buffer_limits(self):
"""Get the high and low watermarks for write flow control.
Return a tuple (low, high) where low and high are
positive number of bytes."""
raise NotImplementedError
def write(self, data): def write(self, data):
"""Write some data bytes to the transport. """Write some data bytes to the transport.
@@ -104,7 +119,7 @@ class WriteTransport(BaseTransport):
The default implementation concatenates the arguments and The default implementation concatenates the arguments and
calls write() on the result. calls write() on the result.
""" """
data = compat.flatten_list_bytes(list_of_data) data = b''.join(list_of_data)
self.write(data) self.write(data)
def write_eof(self): def write_eof(self):
@@ -151,10 +166,14 @@ class Transport(ReadTransport, WriteTransport):
except writelines(), which calls write() in a loop. except writelines(), which calls write() in a loop.
""" """
__slots__ = ()
class DatagramTransport(BaseTransport): class DatagramTransport(BaseTransport):
"""Interface for datagram (UDP) transports.""" """Interface for datagram (UDP) transports."""
__slots__ = ()
def sendto(self, data, addr=None): def sendto(self, data, addr=None):
"""Send data to the transport. """Send data to the transport.
@@ -177,6 +196,8 @@ class DatagramTransport(BaseTransport):
class SubprocessTransport(BaseTransport): class SubprocessTransport(BaseTransport):
__slots__ = ()
def get_pid(self): def get_pid(self):
"""Get subprocess id.""" """Get subprocess id."""
raise NotImplementedError raise NotImplementedError
@@ -244,6 +265,8 @@ class _FlowControlMixin(Transport):
resume_writing() may be called. resume_writing() may be called.
""" """
__slots__ = ('_loop', '_protocol_paused', '_high_water', '_low_water')
def __init__(self, extra=None, loop=None): def __init__(self, extra=None, loop=None):
super().__init__(extra) super().__init__(extra)
assert loop is not None assert loop is not None
@@ -259,7 +282,9 @@ class _FlowControlMixin(Transport):
self._protocol_paused = True self._protocol_paused = True
try: try:
self._protocol.pause_writing() self._protocol.pause_writing()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._loop.call_exception_handler({ self._loop.call_exception_handler({
'message': 'protocol.pause_writing() failed', 'message': 'protocol.pause_writing() failed',
'exception': exc, 'exception': exc,
@@ -269,11 +294,13 @@ class _FlowControlMixin(Transport):
def _maybe_resume_protocol(self): def _maybe_resume_protocol(self):
if (self._protocol_paused and if (self._protocol_paused and
self.get_write_buffer_size() <= self._low_water): self.get_write_buffer_size() <= self._low_water):
self._protocol_paused = False self._protocol_paused = False
try: try:
self._protocol.resume_writing() self._protocol.resume_writing()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._loop.call_exception_handler({ self._loop.call_exception_handler({
'message': 'protocol.resume_writing() failed', 'message': 'protocol.resume_writing() failed',
'exception': exc, 'exception': exc,
@@ -287,14 +314,16 @@ class _FlowControlMixin(Transport):
def _set_write_buffer_limits(self, high=None, low=None): def _set_write_buffer_limits(self, high=None, low=None):
if high is None: if high is None:
if low is None: if low is None:
high = 64*1024 high = 64 * 1024
else: else:
high = 4*low high = 4 * low
if low is None: if low is None:
low = high // 4 low = high // 4
if not high >= low >= 0: if not high >= low >= 0:
raise ValueError('high (%r) must be >= low (%r) must be >= 0' % raise ValueError(
(high, low)) f'high ({high!r}) must be >= low ({low!r}) must be >= 0')
self._high_water = high self._high_water = high
self._low_water = low self._low_water = low

98
Lib/asyncio/trsock.py vendored Normal file
View File

@@ -0,0 +1,98 @@
import socket
class TransportSocket:
"""A socket-like wrapper for exposing real transport sockets.
These objects can be safely returned by APIs like
`transport.get_extra_info('socket')`. All potentially disruptive
operations (like "socket.close()") are banned.
"""
__slots__ = ('_sock',)
def __init__(self, sock: socket.socket):
self._sock = sock
@property
def family(self):
return self._sock.family
@property
def type(self):
return self._sock.type
@property
def proto(self):
return self._sock.proto
def __repr__(self):
s = (
f"<asyncio.TransportSocket fd={self.fileno()}, "
f"family={self.family!s}, type={self.type!s}, "
f"proto={self.proto}"
)
if self.fileno() != -1:
try:
laddr = self.getsockname()
if laddr:
s = f"{s}, laddr={laddr}"
except socket.error:
pass
try:
raddr = self.getpeername()
if raddr:
s = f"{s}, raddr={raddr}"
except socket.error:
pass
return f"{s}>"
def __getstate__(self):
raise TypeError("Cannot serialize asyncio.TransportSocket object")
def fileno(self):
return self._sock.fileno()
def dup(self):
return self._sock.dup()
def get_inheritable(self):
return self._sock.get_inheritable()
def shutdown(self, how):
# asyncio doesn't currently provide a high-level transport API
# to shutdown the connection.
self._sock.shutdown(how)
def getsockopt(self, *args, **kwargs):
return self._sock.getsockopt(*args, **kwargs)
def setsockopt(self, *args, **kwargs):
self._sock.setsockopt(*args, **kwargs)
def getpeername(self):
return self._sock.getpeername()
def getsockname(self):
return self._sock.getsockname()
def getsockbyname(self):
return self._sock.getsockbyname()
def settimeout(self, value):
if value == 0:
return
raise ValueError(
'settimeout(): only 0 timeout is allowed on transport sockets')
def gettimeout(self):
return 0
def setblocking(self, flag):
if not flag:
return
raise ValueError(
'setblocking(): transport sockets cannot be blocking')

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,41 @@
"""Selector and proactor event loops for Windows.""" """Selector and proactor event loops for Windows."""
import sys
if sys.platform != 'win32': # pragma: no cover
raise ImportError('win32 only')
import _overlapped
import _winapi import _winapi
import errno import errno
from functools import partial
import math import math
import msvcrt
import socket import socket
import struct import struct
import time
import weakref import weakref
from . import events from . import events
from . import base_subprocess from . import base_subprocess
from . import futures from . import futures
from . import exceptions
from . import proactor_events from . import proactor_events
from . import selector_events from . import selector_events
from . import tasks from . import tasks
from . import windows_utils from . import windows_utils
# XXX RustPython TODO: _overlapped
# from . import _overlapped
from .coroutines import coroutine
from .log import logger from .log import logger
__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor', __all__ = (
'DefaultEventLoopPolicy', 'SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
] 'DefaultEventLoopPolicy', 'WindowsSelectorEventLoopPolicy',
'WindowsProactorEventLoopPolicy',
)
NULL = 0 NULL = _winapi.NULL
INFINITE = 0xffffffff INFINITE = _winapi.INFINITE
ERROR_CONNECTION_REFUSED = 1225 ERROR_CONNECTION_REFUSED = 1225
ERROR_CONNECTION_ABORTED = 1236 ERROR_CONNECTION_ABORTED = 1236
@@ -53,7 +62,7 @@ class _OverlappedFuture(futures.Future):
info = super()._repr_info() info = super()._repr_info()
if self._ov is not None: if self._ov is not None:
state = 'pending' if self._ov.pending else 'completed' state = 'pending' if self._ov.pending else 'completed'
info.insert(1, 'overlapped=<%s, %#x>' % (state, self._ov.address)) info.insert(1, f'overlapped=<{state}, {self._ov.address:#x}>')
return info return info
def _cancel_overlapped(self): def _cancel_overlapped(self):
@@ -72,9 +81,9 @@ class _OverlappedFuture(futures.Future):
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
self._ov = None self._ov = None
def cancel(self): def cancel(self, msg=None):
self._cancel_overlapped() self._cancel_overlapped()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
super().set_exception(exception) super().set_exception(exception)
@@ -109,12 +118,12 @@ class _BaseWaitHandleFuture(futures.Future):
def _repr_info(self): def _repr_info(self):
info = super()._repr_info() info = super()._repr_info()
info.append('handle=%#x' % self._handle) info.append(f'handle={self._handle:#x}')
if self._handle is not None: if self._handle is not None:
state = 'signaled' if self._poll() else 'waiting' state = 'signaled' if self._poll() else 'waiting'
info.append(state) info.append(state)
if self._wait_handle is not None: if self._wait_handle is not None:
info.append('wait_handle=%#x' % self._wait_handle) info.append(f'wait_handle={self._wait_handle:#x}')
return info return info
def _unregister_wait_cb(self, fut): def _unregister_wait_cb(self, fut):
@@ -146,9 +155,9 @@ class _BaseWaitHandleFuture(futures.Future):
self._unregister_wait_cb(None) self._unregister_wait_cb(None)
def cancel(self): def cancel(self, msg=None):
self._unregister_wait() self._unregister_wait()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
self._unregister_wait() self._unregister_wait()
@@ -297,9 +306,6 @@ class PipeServer(object):
class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop): class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
"""Windows version of selector event loop.""" """Windows version of selector event loop."""
def _socketpair(self):
return windows_utils.socketpair()
class ProactorEventLoop(proactor_events.BaseProactorEventLoop): class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
"""Windows version of proactor event loop using IOCP.""" """Windows version of proactor event loop using IOCP."""
@@ -309,20 +315,34 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
proactor = IocpProactor() proactor = IocpProactor()
super().__init__(proactor) super().__init__(proactor)
def _socketpair(self): def run_forever(self):
return windows_utils.socketpair() try:
assert self._self_reading_future is None
self.call_soon(self._loop_self_reading)
super().run_forever()
finally:
if self._self_reading_future is not None:
ov = self._self_reading_future._ov
self._self_reading_future.cancel()
# self_reading_future always uses IOCP, so even though it's
# been cancelled, we need to make sure that the IOCP message
# is received so that the kernel is not holding on to the
# memory, possibly causing memory corruption later. Only
# unregister it if IO is complete in all respects. Otherwise
# we need another _poll() later to complete the IO.
if ov is not None and not ov.pending:
self._proactor._unregister(ov)
self._self_reading_future = None
@coroutine async def create_pipe_connection(self, protocol_factory, address):
def create_pipe_connection(self, protocol_factory, address):
f = self._proactor.connect_pipe(address) f = self._proactor.connect_pipe(address)
pipe = yield from f pipe = await f
protocol = protocol_factory() protocol = protocol_factory()
trans = self._make_duplex_pipe_transport(pipe, protocol, trans = self._make_duplex_pipe_transport(pipe, protocol,
extra={'addr': address}) extra={'addr': address})
return trans, protocol return trans, protocol
@coroutine async def start_serving_pipe(self, protocol_factory, address):
def start_serving_pipe(self, protocol_factory, address):
server = PipeServer(address) server = PipeServer(address)
def loop_accept_pipe(f=None): def loop_accept_pipe(f=None):
@@ -347,6 +367,10 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
return return
f = self._proactor.accept_pipe(pipe) f = self._proactor.accept_pipe(pipe)
except BrokenPipeError:
if pipe and pipe.fileno() != -1:
pipe.close()
self.call_soon(loop_accept_pipe)
except OSError as exc: except OSError as exc:
if pipe and pipe.fileno() != -1: if pipe and pipe.fileno() != -1:
self.call_exception_handler({ self.call_exception_handler({
@@ -358,7 +382,8 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
elif self._debug: elif self._debug:
logger.warning("Accept pipe failed on pipe %r", logger.warning("Accept pipe failed on pipe %r",
pipe, exc_info=True) pipe, exc_info=True)
except futures.CancelledError: self.call_soon(loop_accept_pipe)
except exceptions.CancelledError:
if pipe: if pipe:
pipe.close() pipe.close()
else: else:
@@ -368,28 +393,22 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
self.call_soon(loop_accept_pipe) self.call_soon(loop_accept_pipe)
return [server] return [server]
@coroutine async def _make_subprocess_transport(self, protocol, args, shell,
def _make_subprocess_transport(self, protocol, args, shell, stdin, stdout, stderr, bufsize,
stdin, stdout, stderr, bufsize, extra=None, **kwargs):
extra=None, **kwargs):
waiter = self.create_future() waiter = self.create_future()
transp = _WindowsSubprocessTransport(self, protocol, args, shell, transp = _WindowsSubprocessTransport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize, stdin, stdout, stderr, bufsize,
waiter=waiter, extra=extra, waiter=waiter, extra=extra,
**kwargs) **kwargs)
try: try:
yield from waiter await waiter
except Exception as exc: except (SystemExit, KeyboardInterrupt):
# Workaround CPython bug #23353: using yield/yield-from in an raise
# except block of a generator doesn't clear properly sys.exc_info() except BaseException:
err = exc
else:
err = None
if err is not None:
transp.close() transp.close()
yield from transp._wait() await transp._wait()
raise err raise
return transp return transp
@@ -397,7 +416,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
class IocpProactor: class IocpProactor:
"""Proactor implementation using IOCP.""" """Proactor implementation using IOCP."""
def __init__(self, concurrency=0xffffffff): def __init__(self, concurrency=INFINITE):
self._loop = None self._loop = None
self._results = [] self._results = []
self._iocp = _overlapped.CreateIoCompletionPort( self._iocp = _overlapped.CreateIoCompletionPort(
@@ -407,10 +426,16 @@ class IocpProactor:
self._unregistered = [] self._unregistered = []
self._stopped_serving = weakref.WeakSet() self._stopped_serving = weakref.WeakSet()
def _check_closed(self):
if self._iocp is None:
raise RuntimeError('IocpProactor is closed')
def __repr__(self): def __repr__(self):
return ('<%s overlapped#=%s result#=%s>' info = ['overlapped#=%s' % len(self._cache),
% (self.__class__.__name__, len(self._cache), 'result#=%s' % len(self._results)]
len(self._results))) if self._iocp is None:
info.append('closed')
return '<%s %s>' % (self.__class__.__name__, " ".join(info))
def set_loop(self, loop): def set_loop(self, loop):
self._loop = loop self._loop = loop
@@ -420,13 +445,40 @@ class IocpProactor:
self._poll(timeout) self._poll(timeout)
tmp = self._results tmp = self._results
self._results = [] self._results = []
return tmp try:
return tmp
finally:
# Needed to break cycles when an exception occurs.
tmp = None
def _result(self, value): def _result(self, value):
fut = self._loop.create_future() fut = self._loop.create_future()
fut.set_result(value) fut.set_result(value)
return fut return fut
@staticmethod
def finish_socket_func(trans, key, ov):
try:
return ov.getresult()
except OSError as exc:
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
_overlapped.ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)
else:
raise
@classmethod
def _finish_recvfrom(cls, trans, key, ov, *, empty_result):
try:
return cls.finish_socket_func(trans, key, ov)
except OSError as exc:
# WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
# socket is used to send to an address that is not listening.
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
return empty_result, None
else:
raise
def recv(self, conn, nbytes, flags=0): def recv(self, conn, nbytes, flags=0):
self._register_with_iocp(conn) self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL) ov = _overlapped.Overlapped(NULL)
@@ -438,16 +490,50 @@ class IocpProactor:
except BrokenPipeError: except BrokenPipeError:
return self._result(b'') return self._result(b'')
def finish_recv(trans, key, ov): return self._register(ov, conn, self.finish_socket_func)
try:
return ov.getresult()
except OSError as exc:
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
raise ConnectionResetError(*exc.args)
else:
raise
return self._register(ov, conn, finish_recv) def recv_into(self, conn, buf, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
try:
if isinstance(conn, socket.socket):
ov.WSARecvInto(conn.fileno(), buf, flags)
else:
ov.ReadFileInto(conn.fileno(), buf)
except BrokenPipeError:
return self._result(0)
return self._register(ov, conn, self.finish_socket_func)
def recvfrom(self, conn, nbytes, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
try:
ov.WSARecvFrom(conn.fileno(), nbytes, flags)
except BrokenPipeError:
return self._result((b'', None))
return self._register(ov, conn, partial(self._finish_recvfrom,
empty_result=b''))
def recvfrom_into(self, conn, buf, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
try:
ov.WSARecvFromInto(conn.fileno(), buf, flags)
except BrokenPipeError:
return self._result((0, None))
return self._register(ov, conn, partial(self._finish_recvfrom,
empty_result=0))
def sendto(self, conn, buf, flags=0, addr=None):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
ov.WSASendTo(conn.fileno(), buf, flags, addr)
return self._register(ov, conn, self.finish_socket_func)
def send(self, conn, buf, flags=0): def send(self, conn, buf, flags=0):
self._register_with_iocp(conn) self._register_with_iocp(conn)
@@ -457,16 +543,7 @@ class IocpProactor:
else: else:
ov.WriteFile(conn.fileno(), buf) ov.WriteFile(conn.fileno(), buf)
def finish_send(trans, key, ov): return self._register(ov, conn, self.finish_socket_func)
try:
return ov.getresult()
except OSError as exc:
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
raise ConnectionResetError(*exc.args)
else:
raise
return self._register(ov, conn, finish_send)
def accept(self, listener): def accept(self, listener):
self._register_with_iocp(listener) self._register_with_iocp(listener)
@@ -483,12 +560,11 @@ class IocpProactor:
conn.settimeout(listener.gettimeout()) conn.settimeout(listener.gettimeout())
return conn, conn.getpeername() return conn, conn.getpeername()
@coroutine async def accept_coro(future, conn):
def accept_coro(future, conn):
# Coroutine closing the accept socket if the future is cancelled # Coroutine closing the accept socket if the future is cancelled
try: try:
yield from future await future
except futures.CancelledError: except exceptions.CancelledError:
conn.close() conn.close()
raise raise
@@ -498,6 +574,14 @@ class IocpProactor:
return future return future
def connect(self, conn, address): def connect(self, conn, address):
if conn.type == socket.SOCK_DGRAM:
# WSAConnect will complete immediately for UDP sockets so we don't
# need to register any IOCP operation
_overlapped.WSAConnect(conn.fileno(), address)
fut = self._loop.create_future()
fut.set_result(None)
return fut
self._register_with_iocp(conn) self._register_with_iocp(conn)
# The socket needs to be locally bound before we call ConnectEx(). # The socket needs to be locally bound before we call ConnectEx().
try: try:
@@ -520,6 +604,18 @@ class IocpProactor:
return self._register(ov, conn, finish_connect) return self._register(ov, conn, finish_connect)
def sendfile(self, sock, file, offset, count):
self._register_with_iocp(sock)
ov = _overlapped.Overlapped(NULL)
offset_low = offset & 0xffff_ffff
offset_high = (offset >> 32) & 0xffff_ffff
ov.TransmitFile(sock.fileno(),
msvcrt.get_osfhandle(file.fileno()),
offset_low, offset_high,
count, 0, 0)
return self._register(ov, sock, self.finish_socket_func)
def accept_pipe(self, pipe): def accept_pipe(self, pipe):
self._register_with_iocp(pipe) self._register_with_iocp(pipe)
ov = _overlapped.Overlapped(NULL) ov = _overlapped.Overlapped(NULL)
@@ -537,13 +633,12 @@ class IocpProactor:
return self._register(ov, pipe, finish_accept_pipe) return self._register(ov, pipe, finish_accept_pipe)
@coroutine async def connect_pipe(self, address):
def connect_pipe(self, address):
delay = CONNECT_PIPE_INIT_DELAY delay = CONNECT_PIPE_INIT_DELAY
while True: while True:
# Unfortunately there is no way to do an overlapped connect to a pipe. # Unfortunately there is no way to do an overlapped connect to
# Call CreateFile() in a loop until it doesn't fail with # a pipe. Call CreateFile() in a loop until it doesn't fail with
# ERROR_PIPE_BUSY # ERROR_PIPE_BUSY.
try: try:
handle = _overlapped.ConnectPipe(address) handle = _overlapped.ConnectPipe(address)
break break
@@ -553,7 +648,7 @@ class IocpProactor:
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later # ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY) delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY)
yield from tasks.sleep(delay, loop=self._loop) await tasks.sleep(delay)
return windows_utils.PipeHandle(handle) return windows_utils.PipeHandle(handle)
@@ -573,6 +668,8 @@ class IocpProactor:
return fut return fut
def _wait_for_handle(self, handle, timeout, _is_cancel): def _wait_for_handle(self, handle, timeout, _is_cancel):
self._check_closed()
if timeout is None: if timeout is None:
ms = _winapi.INFINITE ms = _winapi.INFINITE
else: else:
@@ -615,6 +712,8 @@ class IocpProactor:
# that succeed immediately. # that succeed immediately.
def _register(self, ov, obj, callback): def _register(self, ov, obj, callback):
self._check_closed()
# Return a future which will be set with the result of the # Return a future which will be set with the result of the
# operation when it completes. The future's value is actually # operation when it completes. The future's value is actually
# the value returned by callback(). # the value returned by callback().
@@ -651,6 +750,7 @@ class IocpProactor:
already be signalled (pending in the proactor event queue). It is also already be signalled (pending in the proactor event queue). It is also
safe if the event is never signalled (because it was cancelled). safe if the event is never signalled (because it was cancelled).
""" """
self._check_closed()
self._unregistered.append(ov) self._unregistered.append(ov)
def _get_accept_socket(self, family): def _get_accept_socket(self, family):
@@ -707,8 +807,10 @@ class IocpProactor:
else: else:
f.set_result(value) f.set_result(value)
self._results.append(f) self._results.append(f)
finally:
f = None
# Remove unregisted futures # Remove unregistered futures
for ov in self._unregistered: for ov in self._unregistered:
self._cache.pop(ov.address, None) self._cache.pop(ov.address, None)
self._unregistered.clear() self._unregistered.clear()
@@ -720,8 +822,12 @@ class IocpProactor:
self._stopped_serving.add(obj) self._stopped_serving.add(obj)
def close(self): def close(self):
if self._iocp is None:
# already closed
return
# Cancel remaining registered operations. # Cancel remaining registered operations.
for address, (fut, ov, obj, callback) in list(self._cache.items()): for fut, ov, obj, callback in list(self._cache.values()):
if fut.cancelled(): if fut.cancelled():
# Nothing to do with cancelled futures # Nothing to do with cancelled futures
pass pass
@@ -742,14 +848,25 @@ class IocpProactor:
context['source_traceback'] = fut._source_traceback context['source_traceback'] = fut._source_traceback
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
# Wait until all cancelled overlapped complete: don't exit with running
# overlapped to prevent a crash. Display progress every second if the
# loop is still running.
msg_update = 1.0
start_time = time.monotonic()
next_msg = start_time + msg_update
while self._cache: while self._cache:
if not self._poll(1): if next_msg <= time.monotonic():
logger.debug('taking long time to close proactor') logger.debug('%r is running after closing for %.1f seconds',
self, time.monotonic() - start_time)
next_msg = time.monotonic() + msg_update
# handle a few events, or timeout
self._poll(msg_update)
self._results = [] self._results = []
if self._iocp is not None:
_winapi.CloseHandle(self._iocp) _winapi.CloseHandle(self._iocp)
self._iocp = None self._iocp = None
def __del__(self): def __del__(self):
self.close() self.close()
@@ -773,8 +890,12 @@ class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
SelectorEventLoop = _WindowsSelectorEventLoop SelectorEventLoop = _WindowsSelectorEventLoop
class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy): class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
_loop_factory = SelectorEventLoop _loop_factory = SelectorEventLoop
DefaultEventLoopPolicy = _WindowsDefaultEventLoopPolicy class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
_loop_factory = ProactorEventLoop
DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy

View File

@@ -1,6 +1,4 @@
""" """Various Windows specific bits and pieces."""
Various Windows specific bits and pieces
"""
import sys import sys
@@ -11,13 +9,12 @@ import _winapi
import itertools import itertools
import msvcrt import msvcrt
import os import os
import socket
import subprocess import subprocess
import tempfile import tempfile
import warnings import warnings
__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle'] __all__ = 'pipe', 'Popen', 'PIPE', 'PipeHandle'
# Constants/globals # Constants/globals
@@ -29,61 +26,14 @@ STDOUT = subprocess.STDOUT
_mmap_counter = itertools.count() _mmap_counter = itertools.count()
if hasattr(socket, 'socketpair'):
# Since Python 3.5, socket.socketpair() is now also available on Windows
socketpair = socket.socketpair
else:
# Replacement for socket.socketpair()
def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
"""A socket pair usable as a self-pipe, for Windows.
Origin: https://gist.github.com/4325783, by Geert Jansen.
Public domain.
"""
if family == socket.AF_INET:
host = '127.0.0.1'
elif family == socket.AF_INET6:
host = '::1'
else:
raise ValueError("Only AF_INET and AF_INET6 socket address "
"families are supported")
if type != socket.SOCK_STREAM:
raise ValueError("Only SOCK_STREAM socket type is supported")
if proto != 0:
raise ValueError("Only protocol zero is supported")
# We create a connected TCP socket. Note the trick with setblocking(0)
# that prevents us from having to create a thread.
lsock = socket.socket(family, type, proto)
try:
lsock.bind((host, 0))
lsock.listen(1)
# On IPv6, ignore flow_info and scope_id
addr, port = lsock.getsockname()[:2]
csock = socket.socket(family, type, proto)
try:
csock.setblocking(False)
try:
csock.connect((addr, port))
except (BlockingIOError, InterruptedError):
pass
csock.setblocking(True)
ssock, _ = lsock.accept()
except:
csock.close()
raise
finally:
lsock.close()
return (ssock, csock)
# Replacement for os.pipe() using handles instead of fds # Replacement for os.pipe() using handles instead of fds
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE): def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
"""Like os.pipe() but with overlapped support and using handles not fds.""" """Like os.pipe() but with overlapped support and using handles not fds."""
address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' % address = tempfile.mktemp(
(os.getpid(), next(_mmap_counter))) prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
os.getpid(), next(_mmap_counter)))
if duplex: if duplex:
openmode = _winapi.PIPE_ACCESS_DUPLEX openmode = _winapi.PIPE_ACCESS_DUPLEX
@@ -138,10 +88,10 @@ class PipeHandle:
def __repr__(self): def __repr__(self):
if self._handle is not None: if self._handle is not None:
handle = 'handle=%r' % self._handle handle = f'handle={self._handle!r}'
else: else:
handle = 'closed' handle = 'closed'
return '<%s %s>' % (self.__class__.__name__, handle) return f'<{self.__class__.__name__} {handle}>'
@property @property
def handle(self): def handle(self):
@@ -149,7 +99,7 @@ class PipeHandle:
def fileno(self): def fileno(self):
if self._handle is None: if self._handle is None:
raise ValueError("I/O operatioon on closed pipe") raise ValueError("I/O operation on closed pipe")
return self._handle return self._handle
def close(self, *, CloseHandle=_winapi.CloseHandle): def close(self, *, CloseHandle=_winapi.CloseHandle):
@@ -157,10 +107,9 @@ class PipeHandle:
CloseHandle(self._handle) CloseHandle(self._handle)
self._handle = None self._handle = None
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._handle is not None: if self._handle is not None:
warnings.warn("unclosed %r" % self, ResourceWarning, _warn(f"unclosed {self!r}", ResourceWarning, source=self)
source=self)
self.close() self.close()
def __enter__(self): def __enter__(self):

33
Lib/base64.py vendored
View File

@@ -508,14 +508,8 @@ MAXBINSIZE = (MAXLINESIZE//4)*3
def encode(input, output): def encode(input, output):
"""Encode a file; input and output are binary files.""" """Encode a file; input and output are binary files."""
while True: while s := input.read(MAXBINSIZE):
s = input.read(MAXBINSIZE) while len(s) < MAXBINSIZE and (ns := input.read(MAXBINSIZE-len(s))):
if not s:
break
while len(s) < MAXBINSIZE:
ns = input.read(MAXBINSIZE-len(s))
if not ns:
break
s += ns s += ns
line = binascii.b2a_base64(s) line = binascii.b2a_base64(s)
output.write(line) output.write(line)
@@ -523,10 +517,7 @@ def encode(input, output):
def decode(input, output): def decode(input, output):
"""Decode a file; input and output are binary files.""" """Decode a file; input and output are binary files."""
while True: while line := input.readline():
line = input.readline()
if not line:
break
s = binascii.a2b_base64(line) s = binascii.a2b_base64(line)
output.write(s) output.write(s)
@@ -567,13 +558,12 @@ def decodebytes(s):
def main(): def main():
"""Small main program""" """Small main program"""
import sys, getopt import sys, getopt
usage = """usage: %s [-h|-d|-e|-u|-t] [file|-] usage = f"""usage: {sys.argv[0]} [-h|-d|-e|-u] [file|-]
-h: print this help message and exit -h: print this help message and exit
-d, -u: decode -d, -u: decode
-e: encode (default) -e: encode (default)"""
-t: encode and decode string 'Aladdin:open sesame'"""%sys.argv[0]
try: try:
opts, args = getopt.getopt(sys.argv[1:], 'hdeut') opts, args = getopt.getopt(sys.argv[1:], 'hdeu')
except getopt.error as msg: except getopt.error as msg:
sys.stdout = sys.stderr sys.stdout = sys.stderr
print(msg) print(msg)
@@ -584,7 +574,6 @@ def main():
if o == '-e': func = encode if o == '-e': func = encode
if o == '-d': func = decode if o == '-d': func = decode
if o == '-u': func = decode if o == '-u': func = decode
if o == '-t': test(); return
if o == '-h': print(usage); return if o == '-h': print(usage); return
if args and args[0] != '-': if args and args[0] != '-':
with open(args[0], 'rb') as f: with open(args[0], 'rb') as f:
@@ -593,15 +582,5 @@ def main():
func(sys.stdin.buffer, sys.stdout.buffer) func(sys.stdin.buffer, sys.stdout.buffer)
def test():
s0 = b"Aladdin:open sesame"
print(repr(s0))
s1 = encodebytes(s0)
print(repr(s1))
s2 = decodebytes(s1)
print(repr(s2))
assert s0 == s2
if __name__ == '__main__': if __name__ == '__main__':
main() main()

26
Lib/bdb.py vendored
View File

@@ -570,9 +570,12 @@ class Bdb:
rv = frame.f_locals['__return__'] rv = frame.f_locals['__return__']
s += '->' s += '->'
s += reprlib.repr(rv) s += reprlib.repr(rv)
line = linecache.getline(filename, lineno, frame.f_globals) if lineno is not None:
if line: line = linecache.getline(filename, lineno, frame.f_globals)
s += lprefix + line.strip() if line:
s += lprefix + line.strip()
else:
s += f'{lprefix}Warning: lineno is None'
return s return s
# The following methods can be called by clients to use # The following methods can be called by clients to use
@@ -805,15 +808,18 @@ def checkfuncname(b, frame):
return True return True
# Determines if there is an effective (active) breakpoint at this
# line of code. Returns breakpoint number or 0 if none
def effective(file, line, frame): def effective(file, line, frame):
"""Determine which breakpoint for this file:line is to be acted upon. """Return (active breakpoint, delete temporary flag) or (None, None) as
breakpoint to act upon.
Called only if we know there is a breakpoint at this location. Return The "active breakpoint" is the first entry in bplist[line, file] (which
the breakpoint that was triggered and a boolean that indicates if it is must exist) that is enabled, for which checkfuncname is True, and that
ok to delete a temporary breakpoint. Return (None, None) if there is no has neither a False condition nor a positive ignore count. The flag,
matching breakpoint. meaning that a temporary breakpoint should be deleted, is False only
when the condiion cannot be evaluated (in which case, ignore count is
ignored).
If no such entry exists, then (None, None) is returned.
""" """
possibles = Breakpoint.bplist[file, line] possibles = Breakpoint.bplist[file, line]
for b in possibles: for b in possibles:

8
Lib/bisect.py vendored
View File

@@ -8,6 +8,8 @@ def insort_right(a, x, lo=0, hi=None, *, key=None):
Optional args lo (default 0) and hi (default len(a)) bound the Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched. slice of a to be searched.
A custom key function can be supplied to customize the sort order.
""" """
if key is None: if key is None:
lo = bisect_right(a, x, lo, hi) lo = bisect_right(a, x, lo, hi)
@@ -25,6 +27,8 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None):
Optional args lo (default 0) and hi (default len(a)) bound the Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched. slice of a to be searched.
A custom key function can be supplied to customize the sort order.
""" """
if lo < 0: if lo < 0:
@@ -57,6 +61,8 @@ def insort_left(a, x, lo=0, hi=None, *, key=None):
Optional args lo (default 0) and hi (default len(a)) bound the Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched. slice of a to be searched.
A custom key function can be supplied to customize the sort order.
""" """
if key is None: if key is None:
@@ -74,6 +80,8 @@ def bisect_left(a, x, lo=0, hi=None, *, key=None):
Optional args lo (default 0) and hi (default len(a)) bound the Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched. slice of a to be searched.
A custom key function can be supplied to customize the sort order.
""" """
if lo < 0: if lo < 0:

76
Lib/calendar.py vendored
View File

@@ -7,8 +7,10 @@ set the first day of the week (0=Monday, 6=Sunday)."""
import sys import sys
import datetime import datetime
from enum import IntEnum, global_enum
import locale as _locale import locale as _locale
from itertools import repeat from itertools import repeat
import warnings
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
"firstweekday", "isleap", "leapdays", "weekday", "monthrange", "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
@@ -16,6 +18,9 @@ __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
"timegm", "month_name", "month_abbr", "day_name", "day_abbr", "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
"Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
"LocaleHTMLCalendar", "weekheader", "LocaleHTMLCalendar", "weekheader",
"Day", "Month", "JANUARY", "FEBRUARY", "MARCH",
"APRIL", "MAY", "JUNE", "JULY",
"AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
"SATURDAY", "SUNDAY"] "SATURDAY", "SUNDAY"]
@@ -37,9 +42,46 @@ class IllegalWeekdayError(ValueError):
return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
# Constants for months referenced later def __getattr__(name):
January = 1 if name in ('January', 'February'):
February = 2 warnings.warn(f"The '{name}' attribute is deprecated, use '{name.upper()}' instead",
DeprecationWarning, stacklevel=2)
if name == 'January':
return 1
else:
return 2
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
# Constants for months
@global_enum
class Month(IntEnum):
JANUARY = 1
FEBRUARY = 2
MARCH = 3
APRIL = 4
MAY = 5
JUNE = 6
JULY = 7
AUGUST = 8
SEPTEMBER = 9
OCTOBER = 10
NOVEMBER = 11
DECEMBER = 12
# Constants for days
@global_enum
class Day(IntEnum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
# Number of days per month (except for February in leap years) # Number of days per month (except for February in leap years)
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -95,9 +137,6 @@ day_abbr = _localized_day('%a')
month_name = _localized_month('%B') month_name = _localized_month('%B')
month_abbr = _localized_month('%b') month_abbr = _localized_month('%b')
# Constants for weekdays
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
def isleap(year): def isleap(year):
"""Return True for leap years, False for non-leap years.""" """Return True for leap years, False for non-leap years."""
@@ -116,7 +155,7 @@ def weekday(year, month, day):
"""Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31).""" """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
if not datetime.MINYEAR <= year <= datetime.MAXYEAR: if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
year = 2000 + year % 400 year = 2000 + year % 400
return datetime.date(year, month, day).weekday() return Day(datetime.date(year, month, day).weekday())
def monthrange(year, month): def monthrange(year, month):
@@ -125,12 +164,12 @@ def monthrange(year, month):
if not 1 <= month <= 12: if not 1 <= month <= 12:
raise IllegalMonthError(month) raise IllegalMonthError(month)
day1 = weekday(year, month, 1) day1 = weekday(year, month, 1)
ndays = mdays[month] + (month == February and isleap(year)) ndays = mdays[month] + (month == FEBRUARY and isleap(year))
return day1, ndays return day1, ndays
def _monthlen(year, month): def _monthlen(year, month):
return mdays[month] + (month == February and isleap(year)) return mdays[month] + (month == FEBRUARY and isleap(year))
def _prevmonth(year, month): def _prevmonth(year, month):
@@ -260,10 +299,7 @@ class Calendar(object):
Each month contains between 4 and 6 weeks and each week contains 1-7 Each month contains between 4 and 6 weeks and each week contains 1-7
days. Days are datetime.date objects. days. Days are datetime.date objects.
""" """
months = [ months = [self.monthdatescalendar(year, m) for m in Month]
self.monthdatescalendar(year, i)
for i in range(January, January+12)
]
return [months[i:i+width] for i in range(0, len(months), width) ] return [months[i:i+width] for i in range(0, len(months), width) ]
def yeardays2calendar(self, year, width=3): def yeardays2calendar(self, year, width=3):
@@ -273,10 +309,7 @@ class Calendar(object):
(day number, weekday number) tuples. Day numbers outside this month are (day number, weekday number) tuples. Day numbers outside this month are
zero. zero.
""" """
months = [ months = [self.monthdays2calendar(year, m) for m in Month]
self.monthdays2calendar(year, i)
for i in range(January, January+12)
]
return [months[i:i+width] for i in range(0, len(months), width) ] return [months[i:i+width] for i in range(0, len(months), width) ]
def yeardayscalendar(self, year, width=3): def yeardayscalendar(self, year, width=3):
@@ -285,10 +318,7 @@ class Calendar(object):
yeardatescalendar()). Entries in the week lists are day numbers. yeardatescalendar()). Entries in the week lists are day numbers.
Day numbers outside this month are zero. Day numbers outside this month are zero.
""" """
months = [ months = [self.monthdayscalendar(year, m) for m in Month]
self.monthdayscalendar(year, i)
for i in range(January, January+12)
]
return [months[i:i+width] for i in range(0, len(months), width) ] return [months[i:i+width] for i in range(0, len(months), width) ]
@@ -509,7 +539,7 @@ class HTMLCalendar(Calendar):
a('\n') a('\n')
a('<tr><th colspan="%d" class="%s">%s</th></tr>' % ( a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
width, self.cssclass_year_head, theyear)) width, self.cssclass_year_head, theyear))
for i in range(January, January+12, width): for i in range(JANUARY, JANUARY+12, width):
# months in this row # months in this row
months = range(i, min(i+width, 13)) months = range(i, min(i+width, 13))
a('<tr>') a('<tr>')
@@ -693,7 +723,7 @@ def main(args):
parser.add_argument( parser.add_argument(
"-L", "--locale", "-L", "--locale",
default=None, default=None,
help="locale to be used from month and weekday names" help="locale to use for month and weekday names"
) )
parser.add_argument( parser.add_argument(
"-e", "--encoding", "-e", "--encoding",

1012
Lib/cgi.py vendored

File diff suppressed because it is too large Load Diff

2
Lib/cgitb.py vendored
View File

@@ -74,7 +74,7 @@ def lookup(name, frame, locals):
return 'global', frame.f_globals[name] return 'global', frame.f_globals[name]
if '__builtins__' in frame.f_globals: if '__builtins__' in frame.f_globals:
builtins = frame.f_globals['__builtins__'] builtins = frame.f_globals['__builtins__']
if type(builtins) is type({}): if isinstance(builtins, dict):
if name in builtins: if name in builtins:
return 'builtin', builtins[name] return 'builtin', builtins[name]
else: else:

4
Lib/code.py vendored
View File

@@ -106,6 +106,7 @@ class InteractiveInterpreter:
""" """
type, value, tb = sys.exc_info() type, value, tb = sys.exc_info()
sys.last_exc = value
sys.last_type = type sys.last_type = type
sys.last_value = value sys.last_value = value
sys.last_traceback = tb sys.last_traceback = tb
@@ -119,7 +120,7 @@ class InteractiveInterpreter:
else: else:
# Stuff in the right filename # Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line)) value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_value = value sys.last_exc = sys.last_value = value
if sys.excepthook is sys.__excepthook__: if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception_only(type, value) lines = traceback.format_exception_only(type, value)
self.write(''.join(lines)) self.write(''.join(lines))
@@ -138,6 +139,7 @@ class InteractiveInterpreter:
""" """
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info() sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
sys.last_traceback = last_tb sys.last_traceback = last_tb
sys.last_exc = ei[1]
try: try:
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
if sys.excepthook is sys.__excepthook__: if sys.excepthook is sys.__excepthook__:

25
Lib/codecs.py vendored
View File

@@ -414,6 +414,9 @@ class StreamWriter(Codec):
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamReader(Codec): class StreamReader(Codec):
@@ -663,6 +666,9 @@ class StreamReader(Codec):
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamReaderWriter: class StreamReaderWriter:
@@ -750,6 +756,9 @@ class StreamReaderWriter:
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamRecoder: class StreamRecoder:
@@ -866,6 +875,9 @@ class StreamRecoder:
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### Shortcuts ### Shortcuts
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1): def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
@@ -878,7 +890,8 @@ def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
codecs. Output is also codec dependent and will usually be codecs. Output is also codec dependent and will usually be
Unicode as well. Unicode as well.
Underlying encoded files are always opened in binary mode. If encoding is not None, then the
underlying encoded files are always opened in binary mode.
The default file mode is 'r', meaning to open the file in read mode. The default file mode is 'r', meaning to open the file in read mode.
encoding specifies the encoding which is to be used for the encoding specifies the encoding which is to be used for the
@@ -1114,13 +1127,3 @@ except LookupError:
_false = 0 _false = 0
if _false: if _false:
import encodings import encodings
### Tests
if __name__ == '__main__':
# Make stdout translate Latin-1 output into UTF-8 output
sys.stdout = EncodedFile(sys.stdout, 'latin-1', 'utf-8')
# Have stdin translate Latin-1 input into UTF-8 input
sys.stdin = EncodedFile(sys.stdin, 'utf-8', 'latin-1')

20
Lib/codeop.py vendored
View File

@@ -70,8 +70,7 @@ def _maybe_compile(compiler, source, filename, symbol):
return None return None
# fallthrough # fallthrough
return compiler(source, filename, symbol) return compiler(source, filename, symbol, incomplete_input=False)
def _is_syntax_error(err1, err2): def _is_syntax_error(err1, err2):
rep1 = repr(err1) rep1 = repr(err1)
@@ -82,8 +81,13 @@ def _is_syntax_error(err1, err2):
return True return True
return False return False
def _compile(source, filename, symbol): def _compile(source, filename, symbol, incomplete_input=True):
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT) flags = 0
if incomplete_input:
flags |= PyCF_ALLOW_INCOMPLETE_INPUT
flags |= PyCF_DONT_IMPLY_DEDENT
return compile(source, filename, symbol, flags)
def compile_command(source, filename="<input>", symbol="single"): def compile_command(source, filename="<input>", symbol="single"):
r"""Compile a command and determine whether it is incomplete. r"""Compile a command and determine whether it is incomplete.
@@ -114,8 +118,12 @@ class Compile:
def __init__(self): def __init__(self):
self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT
def __call__(self, source, filename, symbol): def __call__(self, source, filename, symbol, **kwargs):
codeob = compile(source, filename, symbol, self.flags, True) flags = self.flags
if kwargs.get('incomplete_input', True) is False:
flags &= ~PyCF_DONT_IMPLY_DEDENT
flags &= ~PyCF_ALLOW_INCOMPLETE_INPUT
codeob = compile(source, filename, symbol, flags, True)
for feature in _features: for feature in _features:
if codeob.co_flags & feature.compiler_flag: if codeob.co_flags & feature.compiler_flag:
self.flags |= feature.compiler_flag self.flags |= feature.compiler_flag

View File

@@ -45,6 +45,11 @@ except ImportError:
else: else:
_collections_abc.MutableSequence.register(deque) _collections_abc.MutableSequence.register(deque)
try:
from _collections import _deque_iterator
except ImportError:
pass
try: try:
from _collections import defaultdict from _collections import defaultdict
except ImportError: except ImportError:
@@ -94,17 +99,19 @@ class OrderedDict(dict):
# Individual links are kept alive by the hard reference in self.__map. # Individual links are kept alive by the hard reference in self.__map.
# Those hard references disappear when a key is deleted from an OrderedDict. # Those hard references disappear when a key is deleted from an OrderedDict.
def __new__(cls, /, *args, **kwds):
"Create the ordered dict object and set up the underlying structures."
self = dict.__new__(cls)
self.__hardroot = _Link()
self.__root = root = _proxy(self.__hardroot)
root.prev = root.next = root
self.__map = {}
return self
def __init__(self, other=(), /, **kwds): def __init__(self, other=(), /, **kwds):
'''Initialize an ordered dictionary. The signature is the same as '''Initialize an ordered dictionary. The signature is the same as
regular dictionaries. Keyword argument order is preserved. regular dictionaries. Keyword argument order is preserved.
''' '''
try:
self.__root
except AttributeError:
self.__hardroot = _Link()
self.__root = root = _proxy(self.__hardroot)
root.prev = root.next = root
self.__map = {}
self.__update(other, **kwds) self.__update(other, **kwds)
def __setitem__(self, key, value, def __setitem__(self, key, value,
@@ -271,7 +278,7 @@ class OrderedDict(dict):
'od.__repr__() <==> repr(od)' 'od.__repr__() <==> repr(od)'
if not self: if not self:
return '%s()' % (self.__class__.__name__,) return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self.items())) return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
def __reduce__(self): def __reduce__(self):
'Return state information for pickling' 'Return state information for pickling'
@@ -511,9 +518,12 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
# specified a particular module. # specified a particular module.
if module is None: if module is None:
try: try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__') module = _sys._getframemodulename(1) or '__main__'
except (AttributeError, ValueError): except AttributeError:
pass try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
if module is not None: if module is not None:
result.__module__ = module result.__module__ = module
@@ -1015,8 +1025,8 @@ class ChainMap(_collections_abc.MutableMapping):
def __iter__(self): def __iter__(self):
d = {} d = {}
for mapping in reversed(self.maps): for mapping in map(dict.fromkeys, reversed(self.maps)):
d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible d |= mapping # reuses stored hash values if possible
return iter(d) return iter(d)
def __contains__(self, key): def __contains__(self, key):
@@ -1136,10 +1146,17 @@ class UserDict(_collections_abc.MutableMapping):
def __iter__(self): def __iter__(self):
return iter(self.data) return iter(self.data)
# Modify __contains__ to work correctly when __missing__ is present # Modify __contains__ and get() to work like dict
# does when __missing__ is present.
def __contains__(self, key): def __contains__(self, key):
return key in self.data return key in self.data
def get(self, key, default=None):
if key in self:
return self[key]
return default
# Now, add the methods in dicts but not in MutableMapping # Now, add the methods in dicts but not in MutableMapping
def __repr__(self): def __repr__(self):
return repr(self.data) return repr(self.data)

2
Lib/colorsys.py vendored
View File

@@ -83,7 +83,7 @@ def rgb_to_hls(r, g, b):
if l <= 0.5: if l <= 0.5:
s = rangec / sumc s = rangec / sumc
else: else:
s = rangec / (2.0-sumc) s = rangec / (2.0-maxc-minc) # Not always 2.0-sumc: gh-106498.
rc = (maxc-r) / rangec rc = (maxc-r) / rangec
gc = (maxc-g) / rangec gc = (maxc-g) / rangec
bc = (maxc-b) / rangec bc = (maxc-b) / rangec

286
Lib/compileall.py vendored
View File

@@ -4,7 +4,7 @@ When called as a script with arguments, this compiles the directories
given as arguments recursively; the -l option prevents it from given as arguments recursively; the -l option prevents it from
recursing into directories. recursing into directories.
Without arguments, if compiles all modules on sys.path, without Without arguments, it compiles all modules on sys.path, without
recursing into subdirectories. (Even though it should do so for recursing into subdirectories. (Even though it should do so for
packages -- for now, you'll have to deal with packages separately.) packages -- for now, you'll have to deal with packages separately.)
@@ -15,16 +15,14 @@ import sys
import importlib.util import importlib.util
import py_compile import py_compile
import struct import struct
import filecmp
try:
from concurrent.futures import ProcessPoolExecutor
except ImportError:
ProcessPoolExecutor = None
from functools import partial from functools import partial
from pathlib import Path
__all__ = ["compile_dir","compile_file","compile_path"] __all__ = ["compile_dir","compile_file","compile_path"]
def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0): def _walk_dir(dir, maxlevels, quiet=0):
if quiet < 2 and isinstance(dir, os.PathLike): if quiet < 2 and isinstance(dir, os.PathLike):
dir = os.fspath(dir) dir = os.fspath(dir)
if not quiet: if not quiet:
@@ -40,59 +38,94 @@ def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
if name == '__pycache__': if name == '__pycache__':
continue continue
fullname = os.path.join(dir, name) fullname = os.path.join(dir, name)
if ddir is not None:
dfile = os.path.join(ddir, name)
else:
dfile = None
if not os.path.isdir(fullname): if not os.path.isdir(fullname):
yield fullname yield fullname
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
os.path.isdir(fullname) and not os.path.islink(fullname)): os.path.isdir(fullname) and not os.path.islink(fullname)):
yield from _walk_dir(fullname, ddir=dfile, yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
maxlevels=maxlevels - 1, quiet=quiet) quiet=quiet)
def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, def compile_dir(dir, maxlevels=None, ddir=None, force=False,
quiet=0, legacy=False, optimize=-1, workers=1): rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
invalidation_mode=None, *, stripdir=None,
prependdir=None, limit_sl_dest=None, hardlink_dupes=False):
"""Byte-compile all modules in the given directory tree. """Byte-compile all modules in the given directory tree.
Arguments (only dir is required): Arguments (only dir is required):
dir: the directory to byte-compile dir: the directory to byte-compile
maxlevels: maximum recursion level (default 10) maxlevels: maximum recursion level (default `sys.getrecursionlimit()`)
ddir: the directory that will be prepended to the path to the ddir: the directory that will be prepended to the path to the
file as it is compiled into each byte-code file. file as it is compiled into each byte-code file.
force: if True, force compilation, even if timestamps are up-to-date force: if True, force compilation, even if timestamps are up-to-date
quiet: full output with False or 0, errors only with 1, quiet: full output with False or 0, errors only with 1,
no output with 2 no output with 2
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter optimize: int or list of optimization levels or -1 for level of
the interpreter. Multiple levels leads to multiple compiled
files each with one optimization level.
workers: maximum number of parallel workers workers: maximum number of parallel workers
invalidation_mode: how the up-to-dateness of the pyc will be checked
stripdir: part of path to left-strip from source file path
prependdir: path to prepend to beginning of original file path, applied
after stripdir
limit_sl_dest: ignore symlinks if they are pointing outside of
the defined path
hardlink_dupes: hardlink duplicated pyc files
""" """
if workers is not None and workers < 0: ProcessPoolExecutor = None
if ddir is not None and (stripdir is not None or prependdir is not None):
raise ValueError(("Destination dir (ddir) cannot be used "
"in combination with stripdir or prependdir"))
if ddir is not None:
stripdir = dir
prependdir = ddir
ddir = None
if workers < 0:
raise ValueError('workers must be greater or equal to 0') raise ValueError('workers must be greater or equal to 0')
if workers != 1:
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels, # Check if this is a system where ProcessPoolExecutor can function.
ddir=ddir) from concurrent.futures.process import _check_system_limits
try:
_check_system_limits()
except NotImplementedError:
workers = 1
else:
from concurrent.futures import ProcessPoolExecutor
if maxlevels is None:
maxlevels = sys.getrecursionlimit()
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
success = True success = True
if workers is not None and workers != 1 and ProcessPoolExecutor is not None: if workers != 1 and ProcessPoolExecutor is not None:
# If workers == 0, let ProcessPoolExecutor choose
workers = workers or None workers = workers or None
with ProcessPoolExecutor(max_workers=workers) as executor: with ProcessPoolExecutor(max_workers=workers) as executor:
results = executor.map(partial(compile_file, results = executor.map(partial(compile_file,
ddir=ddir, force=force, ddir=ddir, force=force,
rx=rx, quiet=quiet, rx=rx, quiet=quiet,
legacy=legacy, legacy=legacy,
optimize=optimize), optimize=optimize,
invalidation_mode=invalidation_mode,
stripdir=stripdir,
prependdir=prependdir,
limit_sl_dest=limit_sl_dest,
hardlink_dupes=hardlink_dupes),
files) files)
success = min(results, default=True) success = min(results, default=True)
else: else:
for file in files: for file in files:
if not compile_file(file, ddir, force, rx, quiet, if not compile_file(file, ddir, force, rx, quiet,
legacy, optimize): legacy, optimize, invalidation_mode,
stripdir=stripdir, prependdir=prependdir,
limit_sl_dest=limit_sl_dest,
hardlink_dupes=hardlink_dupes):
success = False success = False
return success return success
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
legacy=False, optimize=-1): legacy=False, optimize=-1,
invalidation_mode=None, *, stripdir=None, prependdir=None,
limit_sl_dest=None, hardlink_dupes=False):
"""Byte-compile one file. """Byte-compile one file.
Arguments (only fullname is required): Arguments (only fullname is required):
@@ -104,49 +137,114 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
quiet: full output with False or 0, errors only with 1, quiet: full output with False or 0, errors only with 1,
no output with 2 no output with 2
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter optimize: int or list of optimization levels or -1 for level of
the interpreter. Multiple levels leads to multiple compiled
files each with one optimization level.
invalidation_mode: how the up-to-dateness of the pyc will be checked
stripdir: part of path to left-strip from source file path
prependdir: path to prepend to beginning of original file path, applied
after stripdir
limit_sl_dest: ignore symlinks if they are pointing outside of
the defined path.
hardlink_dupes: hardlink duplicated pyc files
""" """
if ddir is not None and (stripdir is not None or prependdir is not None):
raise ValueError(("Destination dir (ddir) cannot be used "
"in combination with stripdir or prependdir"))
success = True success = True
if quiet < 2 and isinstance(fullname, os.PathLike): fullname = os.fspath(fullname)
fullname = os.fspath(fullname) stripdir = os.fspath(stripdir) if stripdir is not None else None
name = os.path.basename(fullname) name = os.path.basename(fullname)
dfile = None
if ddir is not None: if ddir is not None:
dfile = os.path.join(ddir, name) dfile = os.path.join(ddir, name)
else:
dfile = None if stripdir is not None:
fullname_parts = fullname.split(os.path.sep)
stripdir_parts = stripdir.split(os.path.sep)
ddir_parts = list(fullname_parts)
for spart, opart in zip(stripdir_parts, fullname_parts):
if spart == opart:
ddir_parts.remove(spart)
dfile = os.path.join(*ddir_parts)
if prependdir is not None:
if dfile is None:
dfile = os.path.join(prependdir, fullname)
else:
dfile = os.path.join(prependdir, dfile)
if isinstance(optimize, int):
optimize = [optimize]
# Use set() to remove duplicates.
# Use sorted() to create pyc files in a deterministic order.
optimize = sorted(set(optimize))
if hardlink_dupes and len(optimize) < 2:
raise ValueError("Hardlinking of duplicated bytecode makes sense "
"only for more than one optimization level")
if rx is not None: if rx is not None:
mo = rx.search(fullname) mo = rx.search(fullname)
if mo: if mo:
return success return success
if limit_sl_dest is not None and os.path.islink(fullname):
if Path(limit_sl_dest).resolve() not in Path(fullname).resolve().parents:
return success
opt_cfiles = {}
if os.path.isfile(fullname): if os.path.isfile(fullname):
if legacy: for opt_level in optimize:
cfile = fullname + 'c' if legacy:
else: opt_cfiles[opt_level] = fullname + 'c'
if optimize >= 0:
opt = optimize if optimize >= 1 else ''
cfile = importlib.util.cache_from_source(
fullname, optimization=opt)
else: else:
cfile = importlib.util.cache_from_source(fullname) if opt_level >= 0:
cache_dir = os.path.dirname(cfile) opt = opt_level if opt_level >= 1 else ''
cfile = (importlib.util.cache_from_source(
fullname, optimization=opt))
opt_cfiles[opt_level] = cfile
else:
cfile = importlib.util.cache_from_source(fullname)
opt_cfiles[opt_level] = cfile
head, tail = name[:-3], name[-3:] head, tail = name[:-3], name[-3:]
if tail == '.py': if tail == '.py':
if not force: if not force:
try: try:
mtime = int(os.stat(fullname).st_mtime) mtime = int(os.stat(fullname).st_mtime)
expect = struct.pack('<4sl', importlib.util.MAGIC_NUMBER, expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
mtime) 0, mtime & 0xFFFF_FFFF)
with open(cfile, 'rb') as chandle: for cfile in opt_cfiles.values():
actual = chandle.read(8) with open(cfile, 'rb') as chandle:
if expect == actual: actual = chandle.read(12)
if expect != actual:
break
else:
return success return success
except OSError: except OSError:
pass pass
if not quiet: if not quiet:
print('Compiling {!r}...'.format(fullname)) print('Compiling {!r}...'.format(fullname))
try: try:
ok = py_compile.compile(fullname, cfile, dfile, True, for index, opt_level in enumerate(optimize):
optimize=optimize) cfile = opt_cfiles[opt_level]
ok = py_compile.compile(fullname, cfile, dfile, True,
optimize=opt_level,
invalidation_mode=invalidation_mode)
if index > 0 and hardlink_dupes:
previous_cfile = opt_cfiles[optimize[index - 1]]
if filecmp.cmp(cfile, previous_cfile, shallow=False):
os.unlink(cfile)
os.link(previous_cfile, cfile)
except py_compile.PyCompileError as err: except py_compile.PyCompileError as err:
success = False success = False
if quiet >= 2: if quiet >= 2:
@@ -156,9 +254,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
else: else:
print('*** ', end='') print('*** ', end='')
# escape non-printable characters in msg # escape non-printable characters in msg
msg = err.msg.encode(sys.stdout.encoding, encoding = sys.stdout.encoding or sys.getdefaultencoding()
errors='backslashreplace') msg = err.msg.encode(encoding, errors='backslashreplace').decode(encoding)
msg = msg.decode(sys.stdout.encoding)
print(msg) print(msg)
except (SyntaxError, UnicodeError, OSError) as e: except (SyntaxError, UnicodeError, OSError) as e:
success = False success = False
@@ -175,7 +272,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
return success return success
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
legacy=False, optimize=-1): legacy=False, optimize=-1,
invalidation_mode=None):
"""Byte-compile all module on sys.path. """Byte-compile all module on sys.path.
Arguments (all optional): Arguments (all optional):
@@ -186,6 +284,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
quiet: as for compile_dir() (default 0) quiet: as for compile_dir() (default 0)
legacy: as for compile_dir() (default False) legacy: as for compile_dir() (default False)
optimize: as for compile_dir() (default -1) optimize: as for compile_dir() (default -1)
invalidation_mode: as for compiler_dir()
""" """
success = True success = True
for dir in sys.path: for dir in sys.path:
@@ -193,9 +292,16 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
if quiet < 2: if quiet < 2:
print('Skipping current directory') print('Skipping current directory')
else: else:
success = success and compile_dir(dir, maxlevels, None, success = success and compile_dir(
force, quiet=quiet, dir,
legacy=legacy, optimize=optimize) maxlevels,
None,
force,
quiet=quiet,
legacy=legacy,
optimize=optimize,
invalidation_mode=invalidation_mode,
)
return success return success
@@ -206,7 +312,7 @@ def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Utilities to support installing Python libraries.') description='Utilities to support installing Python libraries.')
parser.add_argument('-l', action='store_const', const=0, parser.add_argument('-l', action='store_const', const=0,
default=10, dest='maxlevels', default=None, dest='maxlevels',
help="don't recurse into subdirectories") help="don't recurse into subdirectories")
parser.add_argument('-r', type=int, dest='recursion', parser.add_argument('-r', type=int, dest='recursion',
help=('control the maximum recursion level. ' help=('control the maximum recursion level. '
@@ -224,6 +330,20 @@ def main():
'compile-time tracebacks and in runtime ' 'compile-time tracebacks and in runtime '
'tracebacks in cases where the source file is ' 'tracebacks in cases where the source file is '
'unavailable')) 'unavailable'))
parser.add_argument('-s', metavar='STRIPDIR', dest='stripdir',
default=None,
help=('part of path to left-strip from path '
'to source file - for example buildroot. '
'`-d` and `-s` options cannot be '
'specified together.'))
parser.add_argument('-p', metavar='PREPENDDIR', dest='prependdir',
default=None,
help=('path to add as prefix to path '
'to source file - for example / to make '
'it absolute when some part is removed '
'by `-s` option. '
'`-d` and `-p` options cannot be '
'specified together.'))
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None, parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
help=('skip files matching the regular expression; ' help=('skip files matching the regular expression; '
'the regexp is searched for in the full path ' 'the regexp is searched for in the full path '
@@ -238,6 +358,23 @@ def main():
'to the equivalent of -l sys.path')) 'to the equivalent of -l sys.path'))
parser.add_argument('-j', '--workers', default=1, parser.add_argument('-j', '--workers', default=1,
type=int, help='Run compileall concurrently') type=int, help='Run compileall concurrently')
invalidation_modes = [mode.name.lower().replace('_', '-')
for mode in py_compile.PycInvalidationMode]
parser.add_argument('--invalidation-mode',
choices=sorted(invalidation_modes),
help=('set .pyc invalidation mode; defaults to '
'"checked-hash" if the SOURCE_DATE_EPOCH '
'environment variable is set, and '
'"timestamp" otherwise.'))
parser.add_argument('-o', action='append', type=int, dest='opt_levels',
help=('Optimization levels to run compilation with. '
'Default is -1 which uses the optimization level '
'of the Python interpreter itself (see -O).'))
parser.add_argument('-e', metavar='DIR', dest='limit_sl_dest',
help='Ignore symlinks pointing outsite of the DIR')
parser.add_argument('--hardlink-dupes', action='store_true',
dest='hardlink_dupes',
help='Hardlink duplicated pyc files')
args = parser.parse_args() args = parser.parse_args()
compile_dests = args.compile_dest compile_dests = args.compile_dest
@@ -246,16 +383,31 @@ def main():
import re import re
args.rx = re.compile(args.rx) args.rx = re.compile(args.rx)
if args.limit_sl_dest == "":
args.limit_sl_dest = None
if args.recursion is not None: if args.recursion is not None:
maxlevels = args.recursion maxlevels = args.recursion
else: else:
maxlevels = args.maxlevels maxlevels = args.maxlevels
if args.opt_levels is None:
args.opt_levels = [-1]
if len(args.opt_levels) == 1 and args.hardlink_dupes:
parser.error(("Hardlinking of duplicated bytecode makes sense "
"only for more than one optimization level."))
if args.ddir is not None and (
args.stripdir is not None or args.prependdir is not None
):
parser.error("-d cannot be used in combination with -s or -p")
# if flist is provided then load it # if flist is provided then load it
if args.flist: if args.flist:
try: try:
with (sys.stdin if args.flist=='-' else open(args.flist)) as f: with (sys.stdin if args.flist=='-' else
open(args.flist, encoding="utf-8")) as f:
for line in f: for line in f:
compile_dests.append(line.strip()) compile_dests.append(line.strip())
except OSError: except OSError:
@@ -263,8 +415,11 @@ def main():
print("Error reading file list {}".format(args.flist)) print("Error reading file list {}".format(args.flist))
return False return False
if args.workers is not None: if args.invalidation_mode:
args.workers = args.workers or None ivl_mode = args.invalidation_mode.replace('-', '_').upper()
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
else:
invalidation_mode = None
success = True success = True
try: try:
@@ -272,17 +427,30 @@ def main():
for dest in compile_dests: for dest in compile_dests:
if os.path.isfile(dest): if os.path.isfile(dest):
if not compile_file(dest, args.ddir, args.force, args.rx, if not compile_file(dest, args.ddir, args.force, args.rx,
args.quiet, args.legacy): args.quiet, args.legacy,
invalidation_mode=invalidation_mode,
stripdir=args.stripdir,
prependdir=args.prependdir,
optimize=args.opt_levels,
limit_sl_dest=args.limit_sl_dest,
hardlink_dupes=args.hardlink_dupes):
success = False success = False
else: else:
if not compile_dir(dest, maxlevels, args.ddir, if not compile_dir(dest, maxlevels, args.ddir,
args.force, args.rx, args.quiet, args.force, args.rx, args.quiet,
args.legacy, workers=args.workers): args.legacy, workers=args.workers,
invalidation_mode=invalidation_mode,
stripdir=args.stripdir,
prependdir=args.prependdir,
optimize=args.opt_levels,
limit_sl_dest=args.limit_sl_dest,
hardlink_dupes=args.hardlink_dupes):
success = False success = False
return success return success
else: else:
return compile_path(legacy=args.legacy, force=args.force, return compile_path(legacy=args.legacy, force=args.force,
quiet=args.quiet) quiet=args.quiet,
invalidation_mode=invalidation_mode)
except KeyboardInterrupt: except KeyboardInterrupt:
if args.quiet < 2: if args.quiet < 2:
print("\n[interrupted]") print("\n[interrupted]")

63
Lib/configparser.py vendored
View File

@@ -59,7 +59,7 @@ ConfigParser -- responsible for parsing a list of
instance. It will be used as the handler for option value instance. It will be used as the handler for option value
pre-processing when using getters. RawConfigParser objects don't do pre-processing when using getters. RawConfigParser objects don't do
any sort of interpolation, whereas ConfigParser uses an instance of any sort of interpolation, whereas ConfigParser uses an instance of
BasicInterpolation. The library also provides a ``zc.buildbot`` BasicInterpolation. The library also provides a ``zc.buildout``
inspired ExtendedInterpolation implementation. inspired ExtendedInterpolation implementation.
When `converters` is given, it should be a dictionary where each key When `converters` is given, it should be a dictionary where each key
@@ -149,14 +149,14 @@ import re
import sys import sys
import warnings import warnings
__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", __all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
"NoOptionError", "InterpolationError", "InterpolationDepthError", "NoOptionError", "InterpolationError", "InterpolationDepthError",
"InterpolationMissingOptionError", "InterpolationSyntaxError", "InterpolationMissingOptionError", "InterpolationSyntaxError",
"ParsingError", "MissingSectionHeaderError", "ParsingError", "MissingSectionHeaderError",
"ConfigParser", "SafeConfigParser", "RawConfigParser", "ConfigParser", "RawConfigParser",
"Interpolation", "BasicInterpolation", "ExtendedInterpolation", "Interpolation", "BasicInterpolation", "ExtendedInterpolation",
"LegacyInterpolation", "SectionProxy", "ConverterMapping", "LegacyInterpolation", "SectionProxy", "ConverterMapping",
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH")
_default_dict = dict _default_dict = dict
DEFAULTSECT = "DEFAULT" DEFAULTSECT = "DEFAULT"
@@ -298,41 +298,12 @@ class InterpolationDepthError(InterpolationError):
class ParsingError(Error): class ParsingError(Error):
"""Raised when a configuration file does not follow legal syntax.""" """Raised when a configuration file does not follow legal syntax."""
def __init__(self, source=None, filename=None): def __init__(self, source):
# Exactly one of `source'/`filename' arguments has to be given. super().__init__(f'Source contains parsing errors: {source!r}')
# `filename' kept for compatibility.
if filename and source:
raise ValueError("Cannot specify both `filename' and `source'. "
"Use `source'.")
elif not filename and not source:
raise ValueError("Required argument `source' not given.")
elif filename:
source = filename
Error.__init__(self, 'Source contains parsing errors: %r' % source)
self.source = source self.source = source
self.errors = [] self.errors = []
self.args = (source, ) self.args = (source, )
@property
def filename(self):
"""Deprecated, use `source'."""
warnings.warn(
"The 'filename' attribute will be removed in Python 3.12. "
"Use 'source' instead.",
DeprecationWarning, stacklevel=2
)
return self.source
@filename.setter
def filename(self, value):
"""Deprecated, user `source'."""
warnings.warn(
"The 'filename' attribute will be removed in Python 3.12. "
"Use 'source' instead.",
DeprecationWarning, stacklevel=2
)
self.source = value
def append(self, lineno, line): def append(self, lineno, line):
self.errors.append((lineno, line)) self.errors.append((lineno, line))
self.message += '\n\t[line %2d]: %s' % (lineno, line) self.message += '\n\t[line %2d]: %s' % (lineno, line)
@@ -769,15 +740,6 @@ class RawConfigParser(MutableMapping):
elements_added.add((section, key)) elements_added.add((section, key))
self.set(section, key, value) self.set(section, key, value)
def readfp(self, fp, filename=None):
"""Deprecated, use read_file instead."""
warnings.warn(
"This method will be removed in Python 3.12. "
"Use 'parser.read_file()' instead.",
DeprecationWarning, stacklevel=2
)
self.read_file(fp, source=filename)
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
"""Get an option value for a given section. """Get an option value for a given section.
@@ -1240,19 +1202,6 @@ class ConfigParser(RawConfigParser):
self._interpolation = hold_interpolation self._interpolation = hold_interpolation
class SafeConfigParser(ConfigParser):
"""ConfigParser alias for backwards compatibility purposes."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
warnings.warn(
"The SafeConfigParser class has been renamed to ConfigParser "
"in Python 3.2. This alias will be removed in Python 3.12."
" Use ConfigParser directly instead.",
DeprecationWarning, stacklevel=2
)
class SectionProxy(MutableMapping): class SectionProxy(MutableMapping):
"""A proxy for a single section from a parser.""" """A proxy for a single section from a parser."""

35
Lib/contextlib.py vendored
View File

@@ -145,14 +145,17 @@ class _GeneratorContextManager(
except StopIteration: except StopIteration:
return False return False
else: else:
raise RuntimeError("generator didn't stop") try:
raise RuntimeError("generator didn't stop")
finally:
self.gen.close()
else: else:
if value is None: if value is None:
# Need to force instantiation so we can reliably # Need to force instantiation so we can reliably
# tell if we get the same exception back # tell if we get the same exception back
value = typ() value = typ()
try: try:
self.gen.throw(typ, value, traceback) self.gen.throw(value)
except StopIteration as exc: except StopIteration as exc:
# Suppress StopIteration *unless* it's the same exception that # Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration # was passed to throw(). This prevents a StopIteration
@@ -187,7 +190,10 @@ class _GeneratorContextManager(
raise raise
exc.__traceback__ = traceback exc.__traceback__ = traceback
return False return False
raise RuntimeError("generator didn't stop after throw()") try:
raise RuntimeError("generator didn't stop after throw()")
finally:
self.gen.close()
class _AsyncGeneratorContextManager( class _AsyncGeneratorContextManager(
_GeneratorContextManagerBase, _GeneratorContextManagerBase,
@@ -212,14 +218,17 @@ class _AsyncGeneratorContextManager(
except StopAsyncIteration: except StopAsyncIteration:
return False return False
else: else:
raise RuntimeError("generator didn't stop") try:
raise RuntimeError("generator didn't stop")
finally:
await self.gen.aclose()
else: else:
if value is None: if value is None:
# Need to force instantiation so we can reliably # Need to force instantiation so we can reliably
# tell if we get the same exception back # tell if we get the same exception back
value = typ() value = typ()
try: try:
await self.gen.athrow(typ, value, traceback) await self.gen.athrow(value)
except StopAsyncIteration as exc: except StopAsyncIteration as exc:
# Suppress StopIteration *unless* it's the same exception that # Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration # was passed to throw(). This prevents a StopIteration
@@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager(
raise raise
exc.__traceback__ = traceback exc.__traceback__ = traceback
return False return False
raise RuntimeError("generator didn't stop after athrow()") try:
raise RuntimeError("generator didn't stop after athrow()")
finally:
await self.gen.aclose()
def contextmanager(func): def contextmanager(func):
@@ -441,7 +453,16 @@ class suppress(AbstractContextManager):
# exactly reproduce the limitations of the CPython interpreter. # exactly reproduce the limitations of the CPython interpreter.
# #
# See http://bugs.python.org/issue12029 for more details # See http://bugs.python.org/issue12029 for more details
return exctype is not None and issubclass(exctype, self._exceptions) if exctype is None:
return
if issubclass(exctype, self._exceptions):
return True
if issubclass(exctype, BaseExceptionGroup):
match, rest = excinst.split(self._exceptions)
if rest is None:
return True
raise rest
return False
class _BaseExitStack: class _BaseExitStack:

28
Lib/copy.py vendored
View File

@@ -56,11 +56,6 @@ class Error(Exception):
pass pass
error = Error # backward compatibility error = Error # backward compatibility
try:
from org.python.core import PyStringMap
except ImportError:
PyStringMap = None
__all__ = ["Error", "copy", "deepcopy"] __all__ = ["Error", "copy", "deepcopy"]
def copy(x): def copy(x):
@@ -106,13 +101,11 @@ _copy_dispatch = d = {}
def _copy_immutable(x): def _copy_immutable(x):
return x return x
for t in (type(None), int, float, bool, complex, str, tuple, for t in (types.NoneType, int, float, bool, complex, str, tuple,
bytes, frozenset, type, range, slice, property, bytes, frozenset, type, range, slice, property,
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented), types.BuiltinFunctionType, types.EllipsisType,
types.FunctionType, weakref.ref): types.NotImplementedType, types.FunctionType, types.CodeType,
d[t] = _copy_immutable weakref.ref):
t = getattr(types, "CodeType", None)
if t is not None:
d[t] = _copy_immutable d[t] = _copy_immutable
d[list] = list.copy d[list] = list.copy
@@ -120,9 +113,6 @@ d[dict] = dict.copy
d[set] = set.copy d[set] = set.copy
d[bytearray] = bytearray.copy d[bytearray] = bytearray.copy
if PyStringMap is not None:
d[PyStringMap] = PyStringMap.copy
del d, t del d, t
def deepcopy(x, memo=None, _nil=[]): def deepcopy(x, memo=None, _nil=[]):
@@ -181,9 +171,9 @@ _deepcopy_dispatch = d = {}
def _deepcopy_atomic(x, memo): def _deepcopy_atomic(x, memo):
return x return x
d[type(None)] = _deepcopy_atomic d[types.NoneType] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic d[types.EllipsisType] = _deepcopy_atomic
d[type(NotImplemented)] = _deepcopy_atomic d[types.NotImplementedType] = _deepcopy_atomic
d[int] = _deepcopy_atomic d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic d[bool] = _deepcopy_atomic
@@ -231,8 +221,6 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
y[deepcopy(key, memo)] = deepcopy(value, memo) y[deepcopy(key, memo)] = deepcopy(value, memo)
return y return y
d[dict] = _deepcopy_dict d[dict] = _deepcopy_dict
if PyStringMap is not None:
d[PyStringMap] = _deepcopy_dict
def _deepcopy_method(x, memo): # Copy instance methods def _deepcopy_method(x, memo): # Copy instance methods
return type(x)(x.__func__, deepcopy(x.__self__, memo)) return type(x)(x.__func__, deepcopy(x.__self__, memo))
@@ -301,4 +289,4 @@ def _reconstruct(x, memo, func, args,
y[key] = value y[key] = value
return y return y
del types, weakref, PyStringMap del types, weakref

24
Lib/copyreg.py vendored
View File

@@ -25,16 +25,16 @@ def constructor(object):
# Example: provide pickling support for complex numbers. # Example: provide pickling support for complex numbers.
try: def pickle_complex(c):
complex return complex, (c.real, c.imag)
except NameError:
pass
else:
def pickle_complex(c): pickle(complex, pickle_complex, complex)
return complex, (c.real, c.imag)
pickle(complex, pickle_complex, complex) def pickle_union(obj):
import functools, operator
return functools.reduce, (operator.or_, obj.__args__)
pickle(type(int | str), pickle_union)
# Support for pickling new-style objects # Support for pickling new-style objects
@@ -48,6 +48,7 @@ def _reconstructor(cls, base, state):
return obj return obj
_HEAPTYPE = 1<<9 _HEAPTYPE = 1<<9
_new_type = type(int.__new__)
# Python code for object.__reduce_ex__ for protocols 0 and 1 # Python code for object.__reduce_ex__ for protocols 0 and 1
@@ -57,6 +58,9 @@ def _reduce_ex(self, proto):
for base in cls.__mro__: for base in cls.__mro__:
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE: if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
break break
new = base.__new__
if isinstance(new, _new_type) and new.__self__ is base:
break
else: else:
base = object # not really reachable base = object # not really reachable
if base is object: if base is object:
@@ -79,6 +83,10 @@ def _reduce_ex(self, proto):
except AttributeError: except AttributeError:
dict = None dict = None
else: else:
if (type(self).__getstate__ is object.__getstate__ and
getattr(self, "__slots__", None)):
raise TypeError("a class that defines __slots__ without "
"defining __getstate__ cannot be pickled")
dict = getstate() dict = getstate()
if dict: if dict:
return _reconstructor, args, dict return _reconstructor, args, dict

48
Lib/csv.py vendored
View File

@@ -4,17 +4,22 @@ csv.py - read/write/investigate CSV files
""" """
import re import re
from _csv import Error, writer, reader, \ import types
from _csv import Error, __version__, writer, reader, register_dialect, \
unregister_dialect, get_dialect, list_dialects, \
field_size_limit, \
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \ QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
QUOTE_STRINGS, QUOTE_NOTNULL, \
__doc__ __doc__
from _csv import Dialect as _Dialect
from collections import OrderedDict
from io import StringIO from io import StringIO
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE", __all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
"QUOTE_STRINGS", "QUOTE_NOTNULL",
"Error", "Dialect", "__doc__", "excel", "excel_tab", "Error", "Dialect", "__doc__", "excel", "excel_tab",
"field_size_limit", "reader", "writer", "field_size_limit", "reader", "writer",
"Sniffer", "register_dialect", "get_dialect", "list_dialects", "Sniffer",
"unregister_dialect", "__version__", "DictReader", "DictWriter", "unregister_dialect", "__version__", "DictReader", "DictWriter",
"unix_dialect"] "unix_dialect"]
@@ -57,10 +62,12 @@ class excel(Dialect):
skipinitialspace = False skipinitialspace = False
lineterminator = '\r\n' lineterminator = '\r\n'
quoting = QUOTE_MINIMAL quoting = QUOTE_MINIMAL
register_dialect("excel", excel)
class excel_tab(excel): class excel_tab(excel):
"""Describe the usual properties of Excel-generated TAB-delimited files.""" """Describe the usual properties of Excel-generated TAB-delimited files."""
delimiter = '\t' delimiter = '\t'
register_dialect("excel-tab", excel_tab)
class unix_dialect(Dialect): class unix_dialect(Dialect):
"""Describe the usual properties of Unix-generated CSV files.""" """Describe the usual properties of Unix-generated CSV files."""
@@ -70,11 +77,14 @@ class unix_dialect(Dialect):
skipinitialspace = False skipinitialspace = False
lineterminator = '\n' lineterminator = '\n'
quoting = QUOTE_ALL quoting = QUOTE_ALL
register_dialect("unix", unix_dialect)
class DictReader: class DictReader:
def __init__(self, f, fieldnames=None, restkey=None, restval=None, def __init__(self, f, fieldnames=None, restkey=None, restval=None,
dialect="excel", *args, **kwds): dialect="excel", *args, **kwds):
if fieldnames is not None and iter(fieldnames) is fieldnames:
fieldnames = list(fieldnames)
self._fieldnames = fieldnames # list of keys for the dict self._fieldnames = fieldnames # list of keys for the dict
self.restkey = restkey # key to catch long rows self.restkey = restkey # key to catch long rows
self.restval = restval # default value for short rows self.restval = restval # default value for short rows
@@ -111,7 +121,7 @@ class DictReader:
# values # values
while row == []: while row == []:
row = next(self.reader) row = next(self.reader)
d = OrderedDict(zip(self.fieldnames, row)) d = dict(zip(self.fieldnames, row))
lf = len(self.fieldnames) lf = len(self.fieldnames)
lr = len(row) lr = len(row)
if lf < lr: if lf < lr:
@@ -121,13 +131,18 @@ class DictReader:
d[key] = self.restval d[key] = self.restval
return d return d
__class_getitem__ = classmethod(types.GenericAlias)
class DictWriter: class DictWriter:
def __init__(self, f, fieldnames, restval="", extrasaction="raise", def __init__(self, f, fieldnames, restval="", extrasaction="raise",
dialect="excel", *args, **kwds): dialect="excel", *args, **kwds):
if fieldnames is not None and iter(fieldnames) is fieldnames:
fieldnames = list(fieldnames)
self.fieldnames = fieldnames # list of keys for the dict self.fieldnames = fieldnames # list of keys for the dict
self.restval = restval # for writing short dicts self.restval = restval # for writing short dicts
if extrasaction.lower() not in ("raise", "ignore"): extrasaction = extrasaction.lower()
if extrasaction not in ("raise", "ignore"):
raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'" raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
% extrasaction) % extrasaction)
self.extrasaction = extrasaction self.extrasaction = extrasaction
@@ -135,7 +150,7 @@ class DictWriter:
def writeheader(self): def writeheader(self):
header = dict(zip(self.fieldnames, self.fieldnames)) header = dict(zip(self.fieldnames, self.fieldnames))
self.writerow(header) return self.writerow(header)
def _dict_to_list(self, rowdict): def _dict_to_list(self, rowdict):
if self.extrasaction == "raise": if self.extrasaction == "raise":
@@ -151,11 +166,8 @@ class DictWriter:
def writerows(self, rowdicts): def writerows(self, rowdicts):
return self.writer.writerows(map(self._dict_to_list, rowdicts)) return self.writer.writerows(map(self._dict_to_list, rowdicts))
# Guard Sniffer's type checking against builds that exclude complex() __class_getitem__ = classmethod(types.GenericAlias)
try:
complex
except NameError:
complex = float
class Sniffer: class Sniffer:
''' '''
@@ -404,14 +416,10 @@ class Sniffer:
continue # skip rows that have irregular number of columns continue # skip rows that have irregular number of columns
for col in list(columnTypes.keys()): for col in list(columnTypes.keys()):
thisType = complex
for thisType in [int, float, complex]: try:
try: thisType(row[col])
thisType(row[col]) except (ValueError, OverflowError):
break
except (ValueError, OverflowError):
pass
else:
# fallback to length of string # fallback to length of string
thisType = len(row[col]) thisType = len(row[col])
@@ -427,7 +435,7 @@ class Sniffer:
# on whether it's a header # on whether it's a header
hasHeader = 0 hasHeader = 0
for col, colType in columnTypes.items(): for col, colType in columnTypes.items():
if type(colType) == type(0): # it's a length if isinstance(colType, int): # it's a length
if len(header[col]) != colType: if len(header[col]) != colType:
hasHeader += 1 hasHeader += 1
else: else:

View File

@@ -82,14 +82,6 @@ class NumberTestCase(unittest.TestCase):
self.assertRaises(TypeError, t, "") self.assertRaises(TypeError, t, "")
self.assertRaises(TypeError, t, None) self.assertRaises(TypeError, t, None)
@unittest.skip('test disabled')
def test_valid_ranges(self):
# invalid values of the correct type
# raise ValueError (not OverflowError)
for t, (l, h) in zip(unsigned_types, unsigned_ranges):
self.assertRaises(ValueError, t, l-1)
self.assertRaises(ValueError, t, h+1)
def test_from_param(self): def test_from_param(self):
# the from_param class method attribute always # the from_param class method attribute always
# returns PyCArgObject instances # returns PyCArgObject instances
@@ -106,7 +98,7 @@ class NumberTestCase(unittest.TestCase):
def test_floats(self): def test_floats(self):
# c_float and c_double can be created from # c_float and c_double can be created from
# Python int and float # Python int and float
class FloatLike(object): class FloatLike:
def __float__(self): def __float__(self):
return 2.0 return 2.0
f = FloatLike() f = FloatLike()
@@ -117,15 +109,15 @@ class NumberTestCase(unittest.TestCase):
self.assertEqual(t(f).value, 2.0) self.assertEqual(t(f).value, 2.0)
def test_integers(self): def test_integers(self):
class FloatLike(object): class FloatLike:
def __float__(self): def __float__(self):
return 2.0 return 2.0
f = FloatLike() f = FloatLike()
class IntLike(object): class IntLike:
def __int__(self): def __int__(self):
return 2 return 2
d = IntLike() d = IntLike()
class IndexLike(object): class IndexLike:
def __index__(self): def __index__(self):
return 2 return 2
i = IndexLike() i = IndexLike()
@@ -155,10 +147,10 @@ class NumberTestCase(unittest.TestCase):
# alignment of the type... # alignment of the type...
self.assertEqual((code, alignment(t)), self.assertEqual((code, alignment(t)),
(code, align)) (code, align))
# and alignment of an instance # and alignment of an instance
self.assertEqual((code, alignment(t())), self.assertEqual((code, alignment(t())),
(code, align)) (code, align))
def test_int_from_address(self): def test_int_from_address(self):
from array import array from array import array
@@ -205,19 +197,6 @@ class NumberTestCase(unittest.TestCase):
a[0] = ord('?') a[0] = ord('?')
self.assertEqual(v.value, b'?') self.assertEqual(v.value, b'?')
# array does not support c_bool / 't'
@unittest.skip('test disabled')
def test_bool_from_address(self):
from ctypes import c_bool
from array import array
a = array(c_bool._type_, [True])
v = t.from_address(a.buffer_info()[0])
self.assertEqual(v.value, a[0])
self.assertEqual(type(v) is t)
a[0] = False
self.assertEqual(v.value, a[0])
self.assertEqual(type(v) is t)
def test_init(self): def test_init(self):
# c_int() can be initialized from Python's int, and c_int. # c_int() can be initialized from Python's int, and c_int.
# Not from c_long or so, which seems strange, abc should # Not from c_long or so, which seems strange, abc should
@@ -234,62 +213,6 @@ class NumberTestCase(unittest.TestCase):
if (hasattr(t, "__ctype_le__")): if (hasattr(t, "__ctype_le__")):
self.assertRaises(OverflowError, t.__ctype_le__, big_int) self.assertRaises(OverflowError, t.__ctype_le__, big_int)
@unittest.skip('test disabled')
def test_perf(self):
check_perf()
from ctypes import _SimpleCData
class c_int_S(_SimpleCData):
_type_ = "i"
__slots__ = []
def run_test(rep, msg, func, arg=None):
## items = [None] * rep
items = range(rep)
from time import perf_counter as clock
if arg is not None:
start = clock()
for i in items:
func(arg); func(arg); func(arg); func(arg); func(arg)
stop = clock()
else:
start = clock()
for i in items:
func(); func(); func(); func(); func()
stop = clock()
print("%15s: %.2f us" % (msg, ((stop-start)*1e6/5/rep)))
def check_perf():
# Construct 5 objects
from ctypes import c_int
REP = 200000
run_test(REP, "int()", int)
run_test(REP, "int(999)", int)
run_test(REP, "c_int()", c_int)
run_test(REP, "c_int(999)", c_int)
run_test(REP, "c_int_S()", c_int_S)
run_test(REP, "c_int_S(999)", c_int_S)
# Python 2.3 -OO, win2k, P4 700 MHz:
#
# int(): 0.87 us
# int(999): 0.87 us
# c_int(): 3.35 us
# c_int(999): 3.34 us
# c_int_S(): 3.23 us
# c_int_S(999): 3.24 us
# Python 2.2 -OO, win2k, P4 700 MHz:
#
# int(): 0.89 us
# int(999): 0.89 us
# c_int(): 9.99 us
# c_int(999): 10.02 us
# c_int_S(): 9.87 us
# c_int_S(999): 9.85 us
if __name__ == '__main__': if __name__ == '__main__':
## check_perf()
unittest.main() unittest.main()

2527
Lib/datetime.py vendored

File diff suppressed because it is too large Load Diff

19
Lib/difflib.py vendored
View File

@@ -1200,25 +1200,6 @@ def context_diff(a, b, fromfile='', tofile='',
strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
The modification times are normally expressed in the ISO 8601 format. The modification times are normally expressed in the ISO 8601 format.
If not specified, the strings default to blanks. If not specified, the strings default to blanks.
Example:
>>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(True),
... 'zero\none\ntree\nfour\n'.splitlines(True), 'Original', 'Current')),
... end="")
*** Original
--- Current
***************
*** 1,4 ****
one
! two
! three
four
--- 1,4 ----
+ zero
one
! tree
four
""" """
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm) _check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)

39
Lib/email/message.py vendored
View File

@@ -7,7 +7,6 @@
__all__ = ['Message', 'EmailMessage'] __all__ = ['Message', 'EmailMessage']
import re import re
import uu
import quopri import quopri
from io import BytesIO, StringIO from io import BytesIO, StringIO
@@ -101,6 +100,35 @@ def _unquotevalue(value):
return utils.unquote(value) return utils.unquote(value)
def _decode_uu(encoded):
"""Decode uuencoded data."""
decoded_lines = []
encoded_lines_iter = iter(encoded.splitlines())
for line in encoded_lines_iter:
if line.startswith(b"begin "):
mode, _, path = line.removeprefix(b"begin ").partition(b" ")
try:
int(mode, base=8)
except ValueError:
continue
else:
break
else:
raise ValueError("`begin` line not found")
for line in encoded_lines_iter:
if not line:
raise ValueError("Truncated input")
elif line.strip(b' \t\r\n\f') == b'end':
break
try:
decoded_line = binascii.a2b_uu(line)
except binascii.Error:
# Workaround for broken uuencoders by /Fredrik Lundh
nbytes = (((line[0]-32) & 63) * 4 + 5) // 3
decoded_line = binascii.a2b_uu(line[:nbytes])
decoded_lines.append(decoded_line)
return b''.join(decoded_lines)
class Message: class Message:
"""Basic message object. """Basic message object.
@@ -288,13 +316,10 @@ class Message:
self.policy.handle_defect(self, defect) self.policy.handle_defect(self, defect)
return value return value
elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'): elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
in_file = BytesIO(bpayload)
out_file = BytesIO()
try: try:
uu.decode(in_file, out_file, quiet=True) return _decode_uu(bpayload)
return out_file.getvalue() except ValueError:
except uu.Error: # Some decoding problem.
# Some decoding problem
return bpayload return bpayload
if isinstance(payload, str): if isinstance(payload, str):
return bpayload return bpayload

View File

@@ -156,6 +156,10 @@ def search_function(encoding):
codecs.register(search_function) codecs.register(search_function)
if sys.platform == 'win32': if sys.platform == 'win32':
# bpo-671666, bpo-46668: If Python does not implement a codec for current
# Windows ANSI code page, use the "mbcs" codec instead:
# WideCharToMultiByte() and MultiByteToWideChar() functions with CP_ACP.
# Python does not support custom code pages.
def _alias_mbcs(encoding): def _alias_mbcs(encoding):
try: try:
import _winapi import _winapi

View File

@@ -1,43 +0,0 @@
"""
Code page 65001: Windows UTF-8 (CP_UTF8).
"""
import codecs
import functools
if not hasattr(codecs, 'code_page_encode'):
raise LookupError("cp65001 encoding is only available on Windows")
### Codec APIs
encode = functools.partial(codecs.code_page_encode, 65001)
_decode = functools.partial(codecs.code_page_decode, 65001)
def decode(input, errors='strict'):
return codecs.code_page_decode(65001, input, errors, True)
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
return encode(input, self.errors)[0]
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
_buffer_decode = _decode
class StreamWriter(codecs.StreamWriter):
encode = encode
class StreamReader(codecs.StreamReader):
decode = _decode
### encodings module API
def getregentry():
return codecs.CodecInfo(
name='cp65001',
encode=encode,
decode=decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamreader=StreamReader,
streamwriter=StreamWriter,
)

42
Lib/encodings/idna.py vendored
View File

@@ -39,23 +39,21 @@ def nameprep(label):
# Check bidi # Check bidi
RandAL = [stringprep.in_table_d1(x) for x in label] RandAL = [stringprep.in_table_d1(x) for x in label]
for c in RandAL: if any(RandAL):
if c: # There is a RandAL char in the string. Must perform further
# There is a RandAL char in the string. Must perform further # tests:
# tests: # 1) The characters in section 5.8 MUST be prohibited.
# 1) The characters in section 5.8 MUST be prohibited. # This is table C.8, which was already checked
# This is table C.8, which was already checked # 2) If a string contains any RandALCat character, the string
# 2) If a string contains any RandALCat character, the string # MUST NOT contain any LCat character.
# MUST NOT contain any LCat character. if any(stringprep.in_table_d2(x) for x in label):
if any(stringprep.in_table_d2(x) for x in label): raise UnicodeError("Violation of BIDI requirement 2")
raise UnicodeError("Violation of BIDI requirement 2") # 3) If a string contains any RandALCat character, a
# RandALCat character MUST be the first character of the
# 3) If a string contains any RandALCat character, a # string, and a RandALCat character MUST be the last
# RandALCat character MUST be the first character of the # character of the string.
# string, and a RandALCat character MUST be the last if not RandAL[0] or not RandAL[-1]:
# character of the string. raise UnicodeError("Violation of BIDI requirement 3")
if not RandAL[0] or not RandAL[-1]:
raise UnicodeError("Violation of BIDI requirement 3")
return label return label
@@ -103,6 +101,16 @@ def ToASCII(label):
raise UnicodeError("label empty or too long") raise UnicodeError("label empty or too long")
def ToUnicode(label): def ToUnicode(label):
if len(label) > 1024:
# Protection from https://github.com/python/cpython/issues/98433.
# https://datatracker.ietf.org/doc/html/rfc5894#section-6
# doesn't specify a label size limit prior to NAMEPREP. But having
# one makes practical sense.
# This leaves ample room for nameprep() to remove Nothing characters
# per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still
# preventing us from wasting time decoding a big thing that'll just
# hit the actual <= 63 length limit in Step 6.
raise UnicodeError("label way too long")
# Step 1: Check for ASCII # Step 1: Check for ASCII
if isinstance(label, bytes): if isinstance(label, bytes):
pure_ascii = True pure_ascii = True

View File

@@ -1,307 +0,0 @@
""" Python Character Mapping Codec mac_centeuro generated from 'MAPPINGS/VENDORS/APPLE/CENTEURO.TXT' with gencodec.py.
"""#"
import codecs
### Codec APIs
class Codec(codecs.Codec):
def encode(self,input,errors='strict'):
return codecs.charmap_encode(input,errors,encoding_table)
def decode(self,input,errors='strict'):
return codecs.charmap_decode(input,errors,decoding_table)
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
return codecs.charmap_decode(input,self.errors,decoding_table)[0]
class StreamWriter(Codec,codecs.StreamWriter):
pass
class StreamReader(Codec,codecs.StreamReader):
pass
### encodings module API
def getregentry():
return codecs.CodecInfo(
name='mac-centeuro',
encode=Codec().encode,
decode=Codec().decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamreader=StreamReader,
streamwriter=StreamWriter,
)
### Decoding Table
decoding_table = (
'\x00' # 0x00 -> CONTROL CHARACTER
'\x01' # 0x01 -> CONTROL CHARACTER
'\x02' # 0x02 -> CONTROL CHARACTER
'\x03' # 0x03 -> CONTROL CHARACTER
'\x04' # 0x04 -> CONTROL CHARACTER
'\x05' # 0x05 -> CONTROL CHARACTER
'\x06' # 0x06 -> CONTROL CHARACTER
'\x07' # 0x07 -> CONTROL CHARACTER
'\x08' # 0x08 -> CONTROL CHARACTER
'\t' # 0x09 -> CONTROL CHARACTER
'\n' # 0x0A -> CONTROL CHARACTER
'\x0b' # 0x0B -> CONTROL CHARACTER
'\x0c' # 0x0C -> CONTROL CHARACTER
'\r' # 0x0D -> CONTROL CHARACTER
'\x0e' # 0x0E -> CONTROL CHARACTER
'\x0f' # 0x0F -> CONTROL CHARACTER
'\x10' # 0x10 -> CONTROL CHARACTER
'\x11' # 0x11 -> CONTROL CHARACTER
'\x12' # 0x12 -> CONTROL CHARACTER
'\x13' # 0x13 -> CONTROL CHARACTER
'\x14' # 0x14 -> CONTROL CHARACTER
'\x15' # 0x15 -> CONTROL CHARACTER
'\x16' # 0x16 -> CONTROL CHARACTER
'\x17' # 0x17 -> CONTROL CHARACTER
'\x18' # 0x18 -> CONTROL CHARACTER
'\x19' # 0x19 -> CONTROL CHARACTER
'\x1a' # 0x1A -> CONTROL CHARACTER
'\x1b' # 0x1B -> CONTROL CHARACTER
'\x1c' # 0x1C -> CONTROL CHARACTER
'\x1d' # 0x1D -> CONTROL CHARACTER
'\x1e' # 0x1E -> CONTROL CHARACTER
'\x1f' # 0x1F -> CONTROL CHARACTER
' ' # 0x20 -> SPACE
'!' # 0x21 -> EXCLAMATION MARK
'"' # 0x22 -> QUOTATION MARK
'#' # 0x23 -> NUMBER SIGN
'$' # 0x24 -> DOLLAR SIGN
'%' # 0x25 -> PERCENT SIGN
'&' # 0x26 -> AMPERSAND
"'" # 0x27 -> APOSTROPHE
'(' # 0x28 -> LEFT PARENTHESIS
')' # 0x29 -> RIGHT PARENTHESIS
'*' # 0x2A -> ASTERISK
'+' # 0x2B -> PLUS SIGN
',' # 0x2C -> COMMA
'-' # 0x2D -> HYPHEN-MINUS
'.' # 0x2E -> FULL STOP
'/' # 0x2F -> SOLIDUS
'0' # 0x30 -> DIGIT ZERO
'1' # 0x31 -> DIGIT ONE
'2' # 0x32 -> DIGIT TWO
'3' # 0x33 -> DIGIT THREE
'4' # 0x34 -> DIGIT FOUR
'5' # 0x35 -> DIGIT FIVE
'6' # 0x36 -> DIGIT SIX
'7' # 0x37 -> DIGIT SEVEN
'8' # 0x38 -> DIGIT EIGHT
'9' # 0x39 -> DIGIT NINE
':' # 0x3A -> COLON
';' # 0x3B -> SEMICOLON
'<' # 0x3C -> LESS-THAN SIGN
'=' # 0x3D -> EQUALS SIGN
'>' # 0x3E -> GREATER-THAN SIGN
'?' # 0x3F -> QUESTION MARK
'@' # 0x40 -> COMMERCIAL AT
'A' # 0x41 -> LATIN CAPITAL LETTER A
'B' # 0x42 -> LATIN CAPITAL LETTER B
'C' # 0x43 -> LATIN CAPITAL LETTER C
'D' # 0x44 -> LATIN CAPITAL LETTER D
'E' # 0x45 -> LATIN CAPITAL LETTER E
'F' # 0x46 -> LATIN CAPITAL LETTER F
'G' # 0x47 -> LATIN CAPITAL LETTER G
'H' # 0x48 -> LATIN CAPITAL LETTER H
'I' # 0x49 -> LATIN CAPITAL LETTER I
'J' # 0x4A -> LATIN CAPITAL LETTER J
'K' # 0x4B -> LATIN CAPITAL LETTER K
'L' # 0x4C -> LATIN CAPITAL LETTER L
'M' # 0x4D -> LATIN CAPITAL LETTER M
'N' # 0x4E -> LATIN CAPITAL LETTER N
'O' # 0x4F -> LATIN CAPITAL LETTER O
'P' # 0x50 -> LATIN CAPITAL LETTER P
'Q' # 0x51 -> LATIN CAPITAL LETTER Q
'R' # 0x52 -> LATIN CAPITAL LETTER R
'S' # 0x53 -> LATIN CAPITAL LETTER S
'T' # 0x54 -> LATIN CAPITAL LETTER T
'U' # 0x55 -> LATIN CAPITAL LETTER U
'V' # 0x56 -> LATIN CAPITAL LETTER V
'W' # 0x57 -> LATIN CAPITAL LETTER W
'X' # 0x58 -> LATIN CAPITAL LETTER X
'Y' # 0x59 -> LATIN CAPITAL LETTER Y
'Z' # 0x5A -> LATIN CAPITAL LETTER Z
'[' # 0x5B -> LEFT SQUARE BRACKET
'\\' # 0x5C -> REVERSE SOLIDUS
']' # 0x5D -> RIGHT SQUARE BRACKET
'^' # 0x5E -> CIRCUMFLEX ACCENT
'_' # 0x5F -> LOW LINE
'`' # 0x60 -> GRAVE ACCENT
'a' # 0x61 -> LATIN SMALL LETTER A
'b' # 0x62 -> LATIN SMALL LETTER B
'c' # 0x63 -> LATIN SMALL LETTER C
'd' # 0x64 -> LATIN SMALL LETTER D
'e' # 0x65 -> LATIN SMALL LETTER E
'f' # 0x66 -> LATIN SMALL LETTER F
'g' # 0x67 -> LATIN SMALL LETTER G
'h' # 0x68 -> LATIN SMALL LETTER H
'i' # 0x69 -> LATIN SMALL LETTER I
'j' # 0x6A -> LATIN SMALL LETTER J
'k' # 0x6B -> LATIN SMALL LETTER K
'l' # 0x6C -> LATIN SMALL LETTER L
'm' # 0x6D -> LATIN SMALL LETTER M
'n' # 0x6E -> LATIN SMALL LETTER N
'o' # 0x6F -> LATIN SMALL LETTER O
'p' # 0x70 -> LATIN SMALL LETTER P
'q' # 0x71 -> LATIN SMALL LETTER Q
'r' # 0x72 -> LATIN SMALL LETTER R
's' # 0x73 -> LATIN SMALL LETTER S
't' # 0x74 -> LATIN SMALL LETTER T
'u' # 0x75 -> LATIN SMALL LETTER U
'v' # 0x76 -> LATIN SMALL LETTER V
'w' # 0x77 -> LATIN SMALL LETTER W
'x' # 0x78 -> LATIN SMALL LETTER X
'y' # 0x79 -> LATIN SMALL LETTER Y
'z' # 0x7A -> LATIN SMALL LETTER Z
'{' # 0x7B -> LEFT CURLY BRACKET
'|' # 0x7C -> VERTICAL LINE
'}' # 0x7D -> RIGHT CURLY BRACKET
'~' # 0x7E -> TILDE
'\x7f' # 0x7F -> CONTROL CHARACTER
'\xc4' # 0x80 -> LATIN CAPITAL LETTER A WITH DIAERESIS
'\u0100' # 0x81 -> LATIN CAPITAL LETTER A WITH MACRON
'\u0101' # 0x82 -> LATIN SMALL LETTER A WITH MACRON
'\xc9' # 0x83 -> LATIN CAPITAL LETTER E WITH ACUTE
'\u0104' # 0x84 -> LATIN CAPITAL LETTER A WITH OGONEK
'\xd6' # 0x85 -> LATIN CAPITAL LETTER O WITH DIAERESIS
'\xdc' # 0x86 -> LATIN CAPITAL LETTER U WITH DIAERESIS
'\xe1' # 0x87 -> LATIN SMALL LETTER A WITH ACUTE
'\u0105' # 0x88 -> LATIN SMALL LETTER A WITH OGONEK
'\u010c' # 0x89 -> LATIN CAPITAL LETTER C WITH CARON
'\xe4' # 0x8A -> LATIN SMALL LETTER A WITH DIAERESIS
'\u010d' # 0x8B -> LATIN SMALL LETTER C WITH CARON
'\u0106' # 0x8C -> LATIN CAPITAL LETTER C WITH ACUTE
'\u0107' # 0x8D -> LATIN SMALL LETTER C WITH ACUTE
'\xe9' # 0x8E -> LATIN SMALL LETTER E WITH ACUTE
'\u0179' # 0x8F -> LATIN CAPITAL LETTER Z WITH ACUTE
'\u017a' # 0x90 -> LATIN SMALL LETTER Z WITH ACUTE
'\u010e' # 0x91 -> LATIN CAPITAL LETTER D WITH CARON
'\xed' # 0x92 -> LATIN SMALL LETTER I WITH ACUTE
'\u010f' # 0x93 -> LATIN SMALL LETTER D WITH CARON
'\u0112' # 0x94 -> LATIN CAPITAL LETTER E WITH MACRON
'\u0113' # 0x95 -> LATIN SMALL LETTER E WITH MACRON
'\u0116' # 0x96 -> LATIN CAPITAL LETTER E WITH DOT ABOVE
'\xf3' # 0x97 -> LATIN SMALL LETTER O WITH ACUTE
'\u0117' # 0x98 -> LATIN SMALL LETTER E WITH DOT ABOVE
'\xf4' # 0x99 -> LATIN SMALL LETTER O WITH CIRCUMFLEX
'\xf6' # 0x9A -> LATIN SMALL LETTER O WITH DIAERESIS
'\xf5' # 0x9B -> LATIN SMALL LETTER O WITH TILDE
'\xfa' # 0x9C -> LATIN SMALL LETTER U WITH ACUTE
'\u011a' # 0x9D -> LATIN CAPITAL LETTER E WITH CARON
'\u011b' # 0x9E -> LATIN SMALL LETTER E WITH CARON
'\xfc' # 0x9F -> LATIN SMALL LETTER U WITH DIAERESIS
'\u2020' # 0xA0 -> DAGGER
'\xb0' # 0xA1 -> DEGREE SIGN
'\u0118' # 0xA2 -> LATIN CAPITAL LETTER E WITH OGONEK
'\xa3' # 0xA3 -> POUND SIGN
'\xa7' # 0xA4 -> SECTION SIGN
'\u2022' # 0xA5 -> BULLET
'\xb6' # 0xA6 -> PILCROW SIGN
'\xdf' # 0xA7 -> LATIN SMALL LETTER SHARP S
'\xae' # 0xA8 -> REGISTERED SIGN
'\xa9' # 0xA9 -> COPYRIGHT SIGN
'\u2122' # 0xAA -> TRADE MARK SIGN
'\u0119' # 0xAB -> LATIN SMALL LETTER E WITH OGONEK
'\xa8' # 0xAC -> DIAERESIS
'\u2260' # 0xAD -> NOT EQUAL TO
'\u0123' # 0xAE -> LATIN SMALL LETTER G WITH CEDILLA
'\u012e' # 0xAF -> LATIN CAPITAL LETTER I WITH OGONEK
'\u012f' # 0xB0 -> LATIN SMALL LETTER I WITH OGONEK
'\u012a' # 0xB1 -> LATIN CAPITAL LETTER I WITH MACRON
'\u2264' # 0xB2 -> LESS-THAN OR EQUAL TO
'\u2265' # 0xB3 -> GREATER-THAN OR EQUAL TO
'\u012b' # 0xB4 -> LATIN SMALL LETTER I WITH MACRON
'\u0136' # 0xB5 -> LATIN CAPITAL LETTER K WITH CEDILLA
'\u2202' # 0xB6 -> PARTIAL DIFFERENTIAL
'\u2211' # 0xB7 -> N-ARY SUMMATION
'\u0142' # 0xB8 -> LATIN SMALL LETTER L WITH STROKE
'\u013b' # 0xB9 -> LATIN CAPITAL LETTER L WITH CEDILLA
'\u013c' # 0xBA -> LATIN SMALL LETTER L WITH CEDILLA
'\u013d' # 0xBB -> LATIN CAPITAL LETTER L WITH CARON
'\u013e' # 0xBC -> LATIN SMALL LETTER L WITH CARON
'\u0139' # 0xBD -> LATIN CAPITAL LETTER L WITH ACUTE
'\u013a' # 0xBE -> LATIN SMALL LETTER L WITH ACUTE
'\u0145' # 0xBF -> LATIN CAPITAL LETTER N WITH CEDILLA
'\u0146' # 0xC0 -> LATIN SMALL LETTER N WITH CEDILLA
'\u0143' # 0xC1 -> LATIN CAPITAL LETTER N WITH ACUTE
'\xac' # 0xC2 -> NOT SIGN
'\u221a' # 0xC3 -> SQUARE ROOT
'\u0144' # 0xC4 -> LATIN SMALL LETTER N WITH ACUTE
'\u0147' # 0xC5 -> LATIN CAPITAL LETTER N WITH CARON
'\u2206' # 0xC6 -> INCREMENT
'\xab' # 0xC7 -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
'\xbb' # 0xC8 -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
'\u2026' # 0xC9 -> HORIZONTAL ELLIPSIS
'\xa0' # 0xCA -> NO-BREAK SPACE
'\u0148' # 0xCB -> LATIN SMALL LETTER N WITH CARON
'\u0150' # 0xCC -> LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
'\xd5' # 0xCD -> LATIN CAPITAL LETTER O WITH TILDE
'\u0151' # 0xCE -> LATIN SMALL LETTER O WITH DOUBLE ACUTE
'\u014c' # 0xCF -> LATIN CAPITAL LETTER O WITH MACRON
'\u2013' # 0xD0 -> EN DASH
'\u2014' # 0xD1 -> EM DASH
'\u201c' # 0xD2 -> LEFT DOUBLE QUOTATION MARK
'\u201d' # 0xD3 -> RIGHT DOUBLE QUOTATION MARK
'\u2018' # 0xD4 -> LEFT SINGLE QUOTATION MARK
'\u2019' # 0xD5 -> RIGHT SINGLE QUOTATION MARK
'\xf7' # 0xD6 -> DIVISION SIGN
'\u25ca' # 0xD7 -> LOZENGE
'\u014d' # 0xD8 -> LATIN SMALL LETTER O WITH MACRON
'\u0154' # 0xD9 -> LATIN CAPITAL LETTER R WITH ACUTE
'\u0155' # 0xDA -> LATIN SMALL LETTER R WITH ACUTE
'\u0158' # 0xDB -> LATIN CAPITAL LETTER R WITH CARON
'\u2039' # 0xDC -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK
'\u203a' # 0xDD -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
'\u0159' # 0xDE -> LATIN SMALL LETTER R WITH CARON
'\u0156' # 0xDF -> LATIN CAPITAL LETTER R WITH CEDILLA
'\u0157' # 0xE0 -> LATIN SMALL LETTER R WITH CEDILLA
'\u0160' # 0xE1 -> LATIN CAPITAL LETTER S WITH CARON
'\u201a' # 0xE2 -> SINGLE LOW-9 QUOTATION MARK
'\u201e' # 0xE3 -> DOUBLE LOW-9 QUOTATION MARK
'\u0161' # 0xE4 -> LATIN SMALL LETTER S WITH CARON
'\u015a' # 0xE5 -> LATIN CAPITAL LETTER S WITH ACUTE
'\u015b' # 0xE6 -> LATIN SMALL LETTER S WITH ACUTE
'\xc1' # 0xE7 -> LATIN CAPITAL LETTER A WITH ACUTE
'\u0164' # 0xE8 -> LATIN CAPITAL LETTER T WITH CARON
'\u0165' # 0xE9 -> LATIN SMALL LETTER T WITH CARON
'\xcd' # 0xEA -> LATIN CAPITAL LETTER I WITH ACUTE
'\u017d' # 0xEB -> LATIN CAPITAL LETTER Z WITH CARON
'\u017e' # 0xEC -> LATIN SMALL LETTER Z WITH CARON
'\u016a' # 0xED -> LATIN CAPITAL LETTER U WITH MACRON
'\xd3' # 0xEE -> LATIN CAPITAL LETTER O WITH ACUTE
'\xd4' # 0xEF -> LATIN CAPITAL LETTER O WITH CIRCUMFLEX
'\u016b' # 0xF0 -> LATIN SMALL LETTER U WITH MACRON
'\u016e' # 0xF1 -> LATIN CAPITAL LETTER U WITH RING ABOVE
'\xda' # 0xF2 -> LATIN CAPITAL LETTER U WITH ACUTE
'\u016f' # 0xF3 -> LATIN SMALL LETTER U WITH RING ABOVE
'\u0170' # 0xF4 -> LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
'\u0171' # 0xF5 -> LATIN SMALL LETTER U WITH DOUBLE ACUTE
'\u0172' # 0xF6 -> LATIN CAPITAL LETTER U WITH OGONEK
'\u0173' # 0xF7 -> LATIN SMALL LETTER U WITH OGONEK
'\xdd' # 0xF8 -> LATIN CAPITAL LETTER Y WITH ACUTE
'\xfd' # 0xF9 -> LATIN SMALL LETTER Y WITH ACUTE
'\u0137' # 0xFA -> LATIN SMALL LETTER K WITH CEDILLA
'\u017b' # 0xFB -> LATIN CAPITAL LETTER Z WITH DOT ABOVE
'\u0141' # 0xFC -> LATIN CAPITAL LETTER L WITH STROKE
'\u017c' # 0xFD -> LATIN SMALL LETTER Z WITH DOT ABOVE
'\u0122' # 0xFE -> LATIN CAPITAL LETTER G WITH CEDILLA
'\u02c7' # 0xFF -> CARON
)
### Encoding table
encoding_table=codecs.charmap_build(decoding_table)

View File

@@ -1,45 +0,0 @@
""" Python 'unicode-internal' Codec
Written by Marc-Andre Lemburg (mal@lemburg.com).
(c) Copyright CNRI, All Rights Reserved. NO WARRANTY.
"""
import codecs
### Codec APIs
class Codec(codecs.Codec):
# Note: Binding these as C functions will result in the class not
# converting them to methods. This is intended.
encode = codecs.unicode_internal_encode
decode = codecs.unicode_internal_decode
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
return codecs.unicode_internal_encode(input, self.errors)[0]
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
return codecs.unicode_internal_decode(input, self.errors)[0]
class StreamWriter(Codec,codecs.StreamWriter):
pass
class StreamReader(Codec,codecs.StreamReader):
pass
### encodings module API
def getregentry():
return codecs.CodecInfo(
name='unicode-internal',
encode=Codec.encode,
decode=Codec.decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
)

View File

@@ -9,11 +9,9 @@ from importlib import resources
__all__ = ["version", "bootstrap"] __all__ = ["version", "bootstrap"]
_PACKAGE_NAMES = ('setuptools', 'pip') _PACKAGE_NAMES = ('pip',)
_SETUPTOOLS_VERSION = "65.5.0" _PIP_VERSION = "23.2.1"
_PIP_VERSION = "22.3.1"
_PROJECTS = [ _PROJECTS = [
("setuptools", _SETUPTOOLS_VERSION, "py3"),
("pip", _PIP_VERSION, "py3"), ("pip", _PIP_VERSION, "py3"),
] ]
@@ -153,17 +151,17 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
_disable_pip_configuration_settings() _disable_pip_configuration_settings()
# By default, installing pip and setuptools installs all of the # By default, installing pip installs all of the
# following scripts (X.Y == running Python version): # following scripts (X.Y == running Python version):
# #
# pip, pipX, pipX.Y, easy_install, easy_install-X.Y # pip, pipX, pipX.Y
# #
# pip 1.5+ allows ensurepip to request that some of those be left out # pip 1.5+ allows ensurepip to request that some of those be left out
if altinstall: if altinstall:
# omit pip, pipX and easy_install # omit pip, pipX
os.environ["ENSUREPIP_OPTIONS"] = "altinstall" os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
elif not default_pip: elif not default_pip:
# omit pip and easy_install # omit pip
os.environ["ENSUREPIP_OPTIONS"] = "install" os.environ["ENSUREPIP_OPTIONS"] = "install"
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
@@ -271,14 +269,14 @@ def _main(argv=None):
action="store_true", action="store_true",
default=False, default=False,
help=("Make an alternate install, installing only the X.Y versioned " help=("Make an alternate install, installing only the X.Y versioned "
"scripts (Default: pipX, pipX.Y, easy_install-X.Y)."), "scripts (Default: pipX, pipX.Y)."),
) )
parser.add_argument( parser.add_argument(
"--default-pip", "--default-pip",
action="store_true", action="store_true",
default=False, default=False,
help=("Make a default pip install, installing the unqualified pip " help=("Make a default pip install, installing the unqualified pip "
"and easy_install in addition to the versioned scripts."), "in addition to the versioned scripts."),
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)

2040
Lib/enum.py vendored

File diff suppressed because it is too large Load Diff

13
Lib/filecmp.py vendored
View File

@@ -10,10 +10,7 @@ Functions:
""" """
try: import os
import os
except ImportError:
import _dummy_os as os
import stat import stat
from itertools import filterfalse from itertools import filterfalse
from types import GenericAlias from types import GenericAlias
@@ -160,17 +157,17 @@ class dircmp:
a_path = os.path.join(self.left, x) a_path = os.path.join(self.left, x)
b_path = os.path.join(self.right, x) b_path = os.path.join(self.right, x)
ok = 1 ok = True
try: try:
a_stat = os.stat(a_path) a_stat = os.stat(a_path)
except OSError: except OSError:
# print('Can\'t stat', a_path, ':', why.args[1]) # print('Can\'t stat', a_path, ':', why.args[1])
ok = 0 ok = False
try: try:
b_stat = os.stat(b_path) b_stat = os.stat(b_path)
except OSError: except OSError:
# print('Can\'t stat', b_path, ':', why.args[1]) # print('Can\'t stat', b_path, ':', why.args[1])
ok = 0 ok = False
if ok: if ok:
a_type = stat.S_IFMT(a_stat.st_mode) a_type = stat.S_IFMT(a_stat.st_mode)
@@ -245,7 +242,7 @@ class dircmp:
methodmap = dict(subdirs=phase4, methodmap = dict(subdirs=phase4,
same_files=phase3, diff_files=phase3, funny_files=phase3, same_files=phase3, diff_files=phase3, funny_files=phase3,
common_dirs = phase2, common_files=phase2, common_funny=phase2, common_dirs=phase2, common_files=phase2, common_funny=phase2,
common=phase1, left_only=phase1, right_only=phase1, common=phase1, left_only=phase1, right_only=phase1,
left_list=phase0, right_list=phase0) left_list=phase0, right_list=phase0)

26
Lib/fileinput.py vendored
View File

@@ -217,15 +217,10 @@ class FileInput:
EncodingWarning, 2) EncodingWarning, 2)
# restrict mode argument to reading modes # restrict mode argument to reading modes
if mode not in ('r', 'rU', 'U', 'rb'): if mode not in ('r', 'rb'):
raise ValueError("FileInput opening mode must be one of " raise ValueError("FileInput opening mode must be 'r' or 'rb'")
"'r', 'rU', 'U' and 'rb'")
if 'U' in mode:
import warnings
warnings.warn("'U' mode is deprecated",
DeprecationWarning, 2)
self._mode = mode self._mode = mode
self._write_mode = mode.replace('r', 'w') if 'U' not in mode else 'w' self._write_mode = mode.replace('r', 'w')
if openhook: if openhook:
if inplace: if inplace:
raise ValueError("FileInput cannot use an opening hook in inplace mode") raise ValueError("FileInput cannot use an opening hook in inplace mode")
@@ -262,21 +257,6 @@ class FileInput:
self.nextfile() self.nextfile()
# repeat with next file # repeat with next file
def __getitem__(self, i):
import warnings
warnings.warn(
"Support for indexing FileInput objects is deprecated. "
"Use iterator protocol instead.",
DeprecationWarning,
stacklevel=2
)
if i != self.lineno():
raise RuntimeError("accessing lines out of order")
try:
return self.__next__()
except StopIteration:
raise IndexError("end of input reached")
def nextfile(self): def nextfile(self):
savestdout = self._savestdout savestdout = self._savestdout
self._savestdout = None self._savestdout = None

544
Lib/fractions.py vendored
View File

@@ -1,40 +1,19 @@
# Originally contributed by Sjoerd Mullender. # Originally contributed by Sjoerd Mullender.
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>. # Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
"""Fraction, infinite-precision, real numbers.""" """Fraction, infinite-precision, rational numbers."""
from decimal import Decimal from decimal import Decimal
import functools
import math import math
import numbers import numbers
import operator import operator
import re import re
import sys import sys
__all__ = ['Fraction', 'gcd'] __all__ = ['Fraction']
def gcd(a, b):
"""Calculate the Greatest Common Divisor of a and b.
Unless b==0, the result will have the same sign as b (so that when
b is divided by it, the result comes out positive).
"""
import warnings
warnings.warn('fractions.gcd() is deprecated. Use math.gcd() instead.',
DeprecationWarning, 2)
if type(a) is int is type(b):
if (b or a) < 0:
return -math.gcd(a, b)
return math.gcd(a, b)
return _gcd(a, b)
def _gcd(a, b):
# Supports non-integers for backward compatibility.
while b:
a, b = b, a%b
return a
# Constants related to the hash implementation; hash(x) is based # Constants related to the hash implementation; hash(x) is based
# on the reduction of x modulo the prime _PyHASH_MODULUS. # on the reduction of x modulo the prime _PyHASH_MODULUS.
_PyHASH_MODULUS = sys.hash_info.modulus _PyHASH_MODULUS = sys.hash_info.modulus
@@ -42,21 +21,144 @@ _PyHASH_MODULUS = sys.hash_info.modulus
# _PyHASH_MODULUS. # _PyHASH_MODULUS.
_PyHASH_INF = sys.hash_info.inf _PyHASH_INF = sys.hash_info.inf
@functools.lru_cache(maxsize = 1 << 14)
def _hash_algorithm(numerator, denominator):
# To make sure that the hash of a Fraction agrees with the hash
# of a numerically equal integer, float or Decimal instance, we
# follow the rules for numeric hashes outlined in the
# documentation. (See library docs, 'Built-in Types').
try:
dinv = pow(denominator, -1, _PyHASH_MODULUS)
except ValueError:
# ValueError means there is no modular inverse.
hash_ = _PyHASH_INF
else:
# The general algorithm now specifies that the absolute value of
# the hash is
# (|N| * dinv) % P
# where N is self._numerator and P is _PyHASH_MODULUS. That's
# optimized here in two ways: first, for a non-negative int i,
# hash(i) == i % P, but the int hash implementation doesn't need
# to divide, and is faster than doing % P explicitly. So we do
# hash(|N| * dinv)
# instead. Second, N is unbounded, so its product with dinv may
# be arbitrarily expensive to compute. The final answer is the
# same if we use the bounded |N| % P instead, which can again
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
# so this nested hash() call wastes a bit of time making a
# redundant copy when |N| < P, but can save an arbitrarily large
# amount of computation for large |N|.
hash_ = hash(hash(abs(numerator)) * dinv)
result = hash_ if numerator >= 0 else -hash_
return -2 if result == -1 else result
_RATIONAL_FORMAT = re.compile(r""" _RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start, then \A\s* # optional whitespace at the start,
(?P<sign>[-+]?) # an optional sign, then (?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit (?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*) # numerator (possibly empty) (?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
(?: # followed by (?: # followed by
(?:/(?P<denom>\d+))? # an optional denominator (?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
| # or | # or
(?:\.(?P<decimal>\d*))? # an optional fractional part (?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+))? # and optional exponent (?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
) )
\s*\Z # and optional whitespace to finish \s*\Z # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE) """, re.VERBOSE | re.IGNORECASE)
# Helpers for formatting
def _round_to_exponent(n, d, exponent, no_neg_zero=False):
"""Round a rational number to the nearest multiple of a given power of 10.
Rounds the rational number n/d to the nearest integer multiple of
10**exponent, rounding to the nearest even integer multiple in the case of
a tie. Returns a pair (sign: bool, significand: int) representing the
rounded value (-1)**sign * significand * 10**exponent.
If no_neg_zero is true, then the returned sign will always be False when
the significand is zero. Otherwise, the sign reflects the sign of the
input.
d must be positive, but n and d need not be relatively prime.
"""
if exponent >= 0:
d *= 10**exponent
else:
n *= 10**-exponent
# The divmod quotient is correct for round-ties-towards-positive-infinity;
# In the case of a tie, we zero out the least significant bit of q.
q, r = divmod(n + (d >> 1), d)
if r == 0 and d & 1 == 0:
q &= -2
sign = q < 0 if no_neg_zero else n < 0
return sign, abs(q)
def _round_to_figures(n, d, figures):
"""Round a rational number to a given number of significant figures.
Rounds the rational number n/d to the given number of significant figures
using the round-ties-to-even rule, and returns a triple
(sign: bool, significand: int, exponent: int) representing the rounded
value (-1)**sign * significand * 10**exponent.
In the special case where n = 0, returns a significand of zero and
an exponent of 1 - figures, for compatibility with formatting.
Otherwise, the returned significand satisfies
10**(figures - 1) <= significand < 10**figures.
d must be positive, but n and d need not be relatively prime.
figures must be positive.
"""
# Special case for n == 0.
if n == 0:
return False, 0, 1 - figures
# Find integer m satisfying 10**(m - 1) <= abs(n)/d <= 10**m. (If abs(n)/d
# is a power of 10, either of the two possible values for m is fine.)
str_n, str_d = str(abs(n)), str(d)
m = len(str_n) - len(str_d) + (str_d <= str_n)
# Round to a multiple of 10**(m - figures). The significand we get
# satisfies 10**(figures - 1) <= significand <= 10**figures.
exponent = m - figures
sign, significand = _round_to_exponent(n, d, exponent)
# Adjust in the case where significand == 10**figures, to ensure that
# 10**(figures - 1) <= significand < 10**figures.
if len(str(significand)) == figures + 1:
significand //= 10
exponent += 1
return sign, significand, exponent
# Pattern for matching float-style format specifications;
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?:
(?P<fill>.)?
(?P<align>[<>=^])
)?
(?P<sign>[-+ ]?)
(?P<no_neg_zero>z)?
(?P<alt>\#)?
# A '0' that's *not* followed by another digit is parsed as a minimum width
# rather than a zeropad flag.
(?P<zeropad>0(?=[0-9]))?
(?P<minimumwidth>0|[1-9][0-9]*)?
(?P<thousands_sep>[,_])?
(?:\.(?P<precision>0|[1-9][0-9]*))?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
class Fraction(numbers.Rational): class Fraction(numbers.Rational):
"""This class implements rational numbers. """This class implements rational numbers.
@@ -81,7 +183,7 @@ class Fraction(numbers.Rational):
__slots__ = ('_numerator', '_denominator') __slots__ = ('_numerator', '_denominator')
# We're immutable, so use __new__ not __init__ # We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None, *, _normalize=True): def __new__(cls, numerator=0, denominator=None):
"""Constructs a Rational. """Constructs a Rational.
Takes a string like '3/2' or '1.5', another Rational instance, a Takes a string like '3/2' or '1.5', another Rational instance, a
@@ -144,6 +246,7 @@ class Fraction(numbers.Rational):
denominator = 1 denominator = 1
decimal = m.group('decimal') decimal = m.group('decimal')
if decimal: if decimal:
decimal = decimal.replace('_', '')
scale = 10**len(decimal) scale = 10**len(decimal)
numerator = numerator * scale + int(decimal) numerator = numerator * scale + int(decimal)
denominator *= scale denominator *= scale
@@ -176,16 +279,11 @@ class Fraction(numbers.Rational):
if denominator == 0: if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator) raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
if _normalize: g = math.gcd(numerator, denominator)
if type(numerator) is int is type(denominator): if denominator < 0:
# *very* normal case g = -g
g = math.gcd(numerator, denominator) numerator //= g
if denominator < 0: denominator //= g
g = -g
else:
g = _gcd(numerator, denominator)
numerator //= g
denominator //= g
self._numerator = numerator self._numerator = numerator
self._denominator = denominator self._denominator = denominator
return self return self
@@ -202,7 +300,7 @@ class Fraction(numbers.Rational):
elif not isinstance(f, float): elif not isinstance(f, float):
raise TypeError("%s.from_float() only takes floats, not %r (%s)" % raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
(cls.__name__, f, type(f).__name__)) (cls.__name__, f, type(f).__name__))
return cls(*f.as_integer_ratio()) return cls._from_coprime_ints(*f.as_integer_ratio())
@classmethod @classmethod
def from_decimal(cls, dec): def from_decimal(cls, dec):
@@ -214,13 +312,28 @@ class Fraction(numbers.Rational):
raise TypeError( raise TypeError(
"%s.from_decimal() only takes Decimals, not %r (%s)" % "%s.from_decimal() only takes Decimals, not %r (%s)" %
(cls.__name__, dec, type(dec).__name__)) (cls.__name__, dec, type(dec).__name__))
return cls(*dec.as_integer_ratio()) return cls._from_coprime_ints(*dec.as_integer_ratio())
@classmethod
def _from_coprime_ints(cls, numerator, denominator, /):
"""Convert a pair of ints to a rational number, for internal use.
The ratio of integers should be in lowest terms and the denominator
should be positive.
"""
obj = super(Fraction, cls).__new__(cls)
obj._numerator = numerator
obj._denominator = denominator
return obj
def is_integer(self):
"""Return True if the Fraction is an integer."""
return self._denominator == 1
def as_integer_ratio(self): def as_integer_ratio(self):
"""Return the integer ratio as a tuple. """Return a pair of integers, whose ratio is equal to the original Fraction.
Return a tuple of two integers, whose ratio is equal to the The ratio is in lowest terms and has a positive denominator.
Fraction and with a positive denominator.
""" """
return (self._numerator, self._denominator) return (self._numerator, self._denominator)
@@ -270,14 +383,16 @@ class Fraction(numbers.Rational):
break break
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2 p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
n, d = d, n-a*d n, d = d, n-a*d
k = (max_denominator-q0)//q1 k = (max_denominator-q0)//q1
bound1 = Fraction(p0+k*p1, q0+k*q1)
bound2 = Fraction(p1, q1) # Determine which of the candidates (p0+k*p1)/(q0+k*q1) and p1/q1 is
if abs(bound2 - self) <= abs(bound1-self): # closer to self. The distance between them is 1/(q1*(q0+k*q1)), while
return bound2 # the distance from p1/q1 to self is d/(q1*self._denominator). So we
# need to compare 2*(q0+k*q1) with self._denominator/d.
if 2*d*(q0+k*q1) <= self._denominator:
return Fraction._from_coprime_ints(p1, q1)
else: else:
return bound1 return Fraction._from_coprime_ints(p0+k*p1, q0+k*q1)
@property @property
def numerator(a): def numerator(a):
@@ -299,6 +414,122 @@ class Fraction(numbers.Rational):
else: else:
return '%s/%s' % (self._numerator, self._denominator) return '%s/%s' % (self._numerator, self._denominator)
def __format__(self, format_spec, /):
"""Format this fraction according to the given format specification."""
# Backwards compatiblility with existing formatting.
if not format_spec:
return str(self)
# Validate and parse the format specifier.
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
if match is None:
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}"
)
elif match["align"] is not None and match["zeropad"] is not None:
# Avoid the temptation to guess.
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}; "
"can't use explicit alignment when zero-padding"
)
fill = match["fill"] or " "
align = match["align"] or ">"
pos_sign = "" if match["sign"] == "-" else match["sign"]
no_neg_zero = bool(match["no_neg_zero"])
alternate_form = bool(match["alt"])
zeropad = bool(match["zeropad"])
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"]
precision = int(match["precision"] or "6")
presentation_type = match["presentation_type"]
trim_zeros = presentation_type in "gG" and not alternate_form
trim_point = not alternate_form
exponent_indicator = "E" if presentation_type in "EFG" else "e"
# Round to get the digits we need, figure out where to place the point,
# and decide whether to use scientific notation. 'point_pos' is the
# relative to the _end_ of the digit string: that is, it's the number
# of digits that should follow the point.
if presentation_type in "fF%":
exponent = -precision
if presentation_type == "%":
exponent -= 2
negative, significand = _round_to_exponent(
self._numerator, self._denominator, exponent, no_neg_zero)
scientific = False
point_pos = precision
else: # presentation_type in "eEgG"
figures = (
max(precision, 1)
if presentation_type in "gG"
else precision + 1
)
negative, significand, exponent = _round_to_figures(
self._numerator, self._denominator, figures)
scientific = (
presentation_type in "eE"
or exponent > 0
or exponent + figures <= -4
)
point_pos = figures - 1 if scientific else -exponent
# Get the suffix - the part following the digits, if any.
if presentation_type == "%":
suffix = "%"
elif scientific:
suffix = f"{exponent_indicator}{exponent + point_pos:+03d}"
else:
suffix = ""
# String of output digits, padded sufficiently with zeros on the left
# so that we'll have at least one digit before the decimal point.
digits = f"{significand:0{point_pos + 1}d}"
# Before padding, the output has the form f"{sign}{leading}{trailing}",
# where `leading` includes thousands separators if necessary and
# `trailing` includes the decimal separator where appropriate.
sign = "-" if negative else pos_sign
leading = digits[: len(digits) - point_pos]
frac_part = digits[len(digits) - point_pos :]
if trim_zeros:
frac_part = frac_part.rstrip("0")
separator = "" if trim_point and not frac_part else "."
trailing = separator + frac_part + suffix
# Do zero padding if required.
if zeropad:
min_leading = minimumwidth - len(sign) - len(trailing)
# When adding thousands separators, they'll be added to the
# zero-padded portion too, so we need to compensate.
leading = leading.zfill(
3 * min_leading // 4 + 1 if thousands_sep else min_leading
)
# Insert thousands separators if required.
if thousands_sep:
first_pos = 1 + (len(leading) - 1) % 3
leading = leading[:first_pos] + "".join(
thousands_sep + leading[pos : pos + 3]
for pos in range(first_pos, len(leading), 3)
)
# We now have a sign and a body. Pad with fill character if necessary
# and return.
body = leading + trailing
padding = fill * (minimumwidth - len(sign) - len(body))
if align == ">":
return padding + sign + body
elif align == "<":
return sign + body + padding
elif align == "^":
half = len(padding) // 2
return padding[:half] + sign + body + padding[half:]
else: # align == "="
return sign + padding + body
def _operator_fallbacks(monomorphic_operator, fallback_operator): def _operator_fallbacks(monomorphic_operator, fallback_operator):
"""Generates forward and reverse operators given a purely-rational """Generates forward and reverse operators given a purely-rational
operator and a function from the operator module. operator and a function from the operator module.
@@ -380,8 +611,10 @@ class Fraction(numbers.Rational):
""" """
def forward(a, b): def forward(a, b):
if isinstance(b, (int, Fraction)): if isinstance(b, Fraction):
return monomorphic_operator(a, b) return monomorphic_operator(a, b)
elif isinstance(b, int):
return monomorphic_operator(a, Fraction(b))
elif isinstance(b, float): elif isinstance(b, float):
return fallback_operator(float(a), b) return fallback_operator(float(a), b)
elif isinstance(b, complex): elif isinstance(b, complex):
@@ -394,7 +627,7 @@ class Fraction(numbers.Rational):
def reverse(b, a): def reverse(b, a):
if isinstance(a, numbers.Rational): if isinstance(a, numbers.Rational):
# Includes ints. # Includes ints.
return monomorphic_operator(a, b) return monomorphic_operator(Fraction(a), b)
elif isinstance(a, numbers.Real): elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b)) return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.Complex): elif isinstance(a, numbers.Complex):
@@ -406,32 +639,141 @@ class Fraction(numbers.Rational):
return forward, reverse return forward, reverse
# Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
#
# Assume input fractions a and b are normalized.
#
# 1) Consider addition/subtraction.
#
# Let g = gcd(da, db). Then
#
# na nb na*db ± nb*da
# a ± b == -- ± -- == ------------- ==
# da db da*db
#
# na*(db//g) ± nb*(da//g) t
# == ----------------------- == -
# (da*db)//g d
#
# Now, if g > 1, we're working with smaller integers.
#
# Note, that t, (da//g) and (db//g) are pairwise coprime.
#
# Indeed, (da//g) and (db//g) share no common factors (they were
# removed) and da is coprime with na (since input fractions are
# normalized), hence (da//g) and na are coprime. By symmetry,
# (db//g) and nb are coprime too. Then,
#
# gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
# gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
#
# Above allows us optimize reduction of the result to lowest
# terms. Indeed,
#
# g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
#
# t//g2 t//g2
# a ± b == ----------------------- == ----------------
# (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
#
# is a normalized fraction. This is useful because the unnormalized
# denominator d could be much larger than g.
#
# We should special-case g == 1 (and g2 == 1), since 60.8% of
# randomly-chosen integers are coprime:
# https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
# Note, that g2 == 1 always for fractions, obtained from floats: here
# g is a power of 2 and the unnormalized numerator t is an odd integer.
#
# 2) Consider multiplication
#
# Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
#
# na*nb na*nb (na//g1)*(nb//g2)
# a*b == ----- == ----- == -----------------
# da*db db*da (db//g1)*(da//g2)
#
# Note, that after divisions we're multiplying smaller integers.
#
# Also, the resulting fraction is normalized, because each of
# two factors in the numerator is coprime to each of the two factors
# in the denominator.
#
# Indeed, pick (na//g1). It's coprime with (da//g2), because input
# fractions are normalized. It's also coprime with (db//g1), because
# common factors are removed by g1 == gcd(na, db).
#
# As for addition/subtraction, we should special-case g1 == 1
# and g2 == 1 for same reason. That happens also for multiplying
# rationals, obtained from floats.
def _add(a, b): def _add(a, b):
"""a + b""" """a + b"""
da, db = a.denominator, b.denominator na, da = a._numerator, a._denominator
return Fraction(a.numerator * db + b.numerator * da, nb, db = b._numerator, b._denominator
da * db) g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db + da * nb, da * db)
s = da // g
t = na * (db // g) + nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__add__, __radd__ = _operator_fallbacks(_add, operator.add) __add__, __radd__ = _operator_fallbacks(_add, operator.add)
def _sub(a, b): def _sub(a, b):
"""a - b""" """a - b"""
da, db = a.denominator, b.denominator na, da = a._numerator, a._denominator
return Fraction(a.numerator * db - b.numerator * da, nb, db = b._numerator, b._denominator
da * db) g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db - da * nb, da * db)
s = da // g
t = na * (db // g) - nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
def _mul(a, b): def _mul(a, b):
"""a * b""" """a * b"""
return Fraction(a.numerator * b.numerator, a.denominator * b.denominator) na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g1 = math.gcd(na, db)
if g1 > 1:
na //= g1
db //= g1
g2 = math.gcd(nb, da)
if g2 > 1:
nb //= g2
da //= g2
return Fraction._from_coprime_ints(na * nb, db * da)
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
def _div(a, b): def _div(a, b):
"""a / b""" """a / b"""
return Fraction(a.numerator * b.denominator, # Same as _mul(), with inversed b.
a.denominator * b.numerator) nb, db = b._numerator, b._denominator
if nb == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % db)
na, da = a._numerator, a._denominator
g1 = math.gcd(na, nb)
if g1 > 1:
na //= g1
nb //= g1
g2 = math.gcd(db, da)
if g2 > 1:
da //= g2
db //= g2
n, d = na * db, nb * da
if d < 0:
n, d = -n, -d
return Fraction._from_coprime_ints(n, d)
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
@@ -468,17 +810,17 @@ class Fraction(numbers.Rational):
if b.denominator == 1: if b.denominator == 1:
power = b.numerator power = b.numerator
if power >= 0: if power >= 0:
return Fraction(a._numerator ** power, return Fraction._from_coprime_ints(a._numerator ** power,
a._denominator ** power, a._denominator ** power)
_normalize=False) elif a._numerator > 0:
elif a._numerator >= 0: return Fraction._from_coprime_ints(a._denominator ** -power,
return Fraction(a._denominator ** -power, a._numerator ** -power)
a._numerator ** -power, elif a._numerator == 0:
_normalize=False) raise ZeroDivisionError('Fraction(%s, 0)' %
a._denominator ** -power)
else: else:
return Fraction((-a._denominator) ** -power, return Fraction._from_coprime_ints((-a._denominator) ** -power,
(-a._numerator) ** -power, (-a._numerator) ** -power)
_normalize=False)
else: else:
# A fractional power will generally produce an # A fractional power will generally produce an
# irrational number. # irrational number.
@@ -502,18 +844,25 @@ class Fraction(numbers.Rational):
def __pos__(a): def __pos__(a):
"""+a: Coerces a subclass instance to Fraction""" """+a: Coerces a subclass instance to Fraction"""
return Fraction(a._numerator, a._denominator, _normalize=False) return Fraction._from_coprime_ints(a._numerator, a._denominator)
def __neg__(a): def __neg__(a):
"""-a""" """-a"""
return Fraction(-a._numerator, a._denominator, _normalize=False) return Fraction._from_coprime_ints(-a._numerator, a._denominator)
def __abs__(a): def __abs__(a):
"""abs(a)""" """abs(a)"""
return Fraction(abs(a._numerator), a._denominator, _normalize=False) return Fraction._from_coprime_ints(abs(a._numerator), a._denominator)
def __int__(a, _index=operator.index):
"""int(a)"""
if a._numerator < 0:
return _index(-(-a._numerator // a._denominator))
else:
return _index(a._numerator // a._denominator)
def __trunc__(a): def __trunc__(a):
"""trunc(a)""" """math.trunc(a)"""
if a._numerator < 0: if a._numerator < 0:
return -(-a._numerator // a._denominator) return -(-a._numerator // a._denominator)
else: else:
@@ -521,12 +870,12 @@ class Fraction(numbers.Rational):
def __floor__(a): def __floor__(a):
"""math.floor(a)""" """math.floor(a)"""
return a.numerator // a.denominator return a._numerator // a._denominator
def __ceil__(a): def __ceil__(a):
"""math.ceil(a)""" """math.ceil(a)"""
# The negations cleverly convince floordiv to return the ceiling. # The negations cleverly convince floordiv to return the ceiling.
return -(-a.numerator // a.denominator) return -(-a._numerator // a._denominator)
def __round__(self, ndigits=None): def __round__(self, ndigits=None):
"""round(self, ndigits) """round(self, ndigits)
@@ -534,10 +883,11 @@ class Fraction(numbers.Rational):
Rounds half toward even. Rounds half toward even.
""" """
if ndigits is None: if ndigits is None:
floor, remainder = divmod(self.numerator, self.denominator) d = self._denominator
if remainder * 2 < self.denominator: floor, remainder = divmod(self._numerator, d)
if remainder * 2 < d:
return floor return floor
elif remainder * 2 > self.denominator: elif remainder * 2 > d:
return floor + 1 return floor + 1
# Deal with the half case: # Deal with the half case:
elif floor % 2 == 0: elif floor % 2 == 0:
@@ -555,25 +905,7 @@ class Fraction(numbers.Rational):
def __hash__(self): def __hash__(self):
"""hash(self)""" """hash(self)"""
return _hash_algorithm(self._numerator, self._denominator)
# XXX since this method is expensive, consider caching the result
# In order to make sure that the hash of a Fraction agrees
# with the hash of a numerically equal integer, float or
# Decimal instance, we follow the rules for numeric hashes
# outlined in the documentation. (See library docs, 'Built-in
# Types').
# dinv is the inverse of self._denominator modulo the prime
# _PyHASH_MODULUS, or 0 if self._denominator is divisible by
# _PyHASH_MODULUS.
dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
if not dinv:
hash_ = _PyHASH_INF
else:
hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS
result = hash_ if self >= 0 else -hash_
return -2 if result == -1 else result
def __eq__(a, b): def __eq__(a, b):
"""a == b""" """a == b"""
@@ -643,7 +975,7 @@ class Fraction(numbers.Rational):
# support for pickling, copy, and deepcopy # support for pickling, copy, and deepcopy
def __reduce__(self): def __reduce__(self):
return (self.__class__, (str(self),)) return (self.__class__, (self._numerator, self._denominator))
def __copy__(self): def __copy__(self):
if type(self) == Fraction: if type(self) == Fraction:

85
Lib/ftplib.py vendored
View File

@@ -72,17 +72,17 @@ B_CRLF = b'\r\n'
# The class itself # The class itself
class FTP: class FTP:
'''An FTP client class. '''An FTP client class.
To create a connection, call the class using these arguments: To create a connection, call the class using these arguments:
host, user, passwd, acct, timeout host, user, passwd, acct, timeout, source_address, encoding
The first four arguments are all strings, and have default value ''. The first four arguments are all strings, and have default value ''.
timeout must be numeric and defaults to None if not passed, The parameter ´timeout´ must be numeric and defaults to None if not
meaning that no timeout will be set on any ftp socket(s) passed, meaning that no timeout will be set on any ftp socket(s).
If a timeout is passed, then this is now the default timeout for all ftp If a timeout is passed, then this is now the default timeout for all ftp
socket operations for this instance. socket operations for this instance.
The last parameter is the encoding of filenames, which defaults to utf-8.
Then use self.connect() with optional host and port argument. Then use self.connect() with optional host and port argument.
@@ -102,15 +102,19 @@ class FTP:
sock = None sock = None
file = None file = None
welcome = None welcome = None
passiveserver = 1 passiveserver = True
encoding = "latin-1" # Disables https://bugs.python.org/issue43285 security if set to True.
trust_server_pasv_ipv4_address = False
# Initialization method (called by class instantiation).
# Initialize host to localhost, port to standard ftp port
# Optional arguments are host (for connect()),
# and user, passwd, acct (for login())
def __init__(self, host='', user='', passwd='', acct='', def __init__(self, host='', user='', passwd='', acct='',
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None): timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
encoding='utf-8'):
"""Initialization method (called by class instantiation).
Initialize host to localhost, port to standard ftp port.
Optional arguments are host (for connect()),
and user, passwd, acct (for login()).
"""
self.encoding = encoding
self.source_address = source_address self.source_address = source_address
self.timeout = timeout self.timeout = timeout
if host: if host:
@@ -146,6 +150,8 @@ class FTP:
self.port = port self.port = port
if timeout != -999: if timeout != -999:
self.timeout = timeout self.timeout = timeout
if self.timeout is not None and not self.timeout:
raise ValueError('Non-blocking socket (timeout=0) is not supported')
if source_address is not None: if source_address is not None:
self.source_address = source_address self.source_address = source_address
sys.audit("ftplib.connect", self, self.host, self.port) sys.audit("ftplib.connect", self, self.host, self.port)
@@ -316,8 +322,13 @@ class FTP:
return sock return sock
def makepasv(self): def makepasv(self):
"""Internal: Does the PASV or EPSV handshake -> (address, port)"""
if self.af == socket.AF_INET: if self.af == socket.AF_INET:
host, port = parse227(self.sendcmd('PASV')) untrusted_host, port = parse227(self.sendcmd('PASV'))
if self.trust_server_pasv_ipv4_address:
host = untrusted_host
else:
host = self.sock.getpeername()[0]
else: else:
host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
return host, port return host, port
@@ -423,10 +434,7 @@ class FTP:
""" """
self.voidcmd('TYPE I') self.voidcmd('TYPE I')
with self.transfercmd(cmd, rest) as conn: with self.transfercmd(cmd, rest) as conn:
while 1: while data := conn.recv(blocksize):
data = conn.recv(blocksize)
if not data:
break
callback(data) callback(data)
# shutdown ssl layer # shutdown ssl layer
if _SSLSocket is not None and isinstance(conn, _SSLSocket): if _SSLSocket is not None and isinstance(conn, _SSLSocket):
@@ -485,10 +493,7 @@ class FTP:
""" """
self.voidcmd('TYPE I') self.voidcmd('TYPE I')
with self.transfercmd(cmd, rest) as conn: with self.transfercmd(cmd, rest) as conn:
while 1: while buf := fp.read(blocksize):
buf = fp.read(blocksize)
if not buf:
break
conn.sendall(buf) conn.sendall(buf)
if callback: if callback:
callback(buf) callback(buf)
@@ -550,7 +555,7 @@ class FTP:
LIST command. (This *should* only be used for a pathname.)''' LIST command. (This *should* only be used for a pathname.)'''
cmd = 'LIST' cmd = 'LIST'
func = None func = None
if args[-1:] and type(args[-1]) != type(''): if args[-1:] and not isinstance(args[-1], str):
args, func = args[:-1], args[-1] args, func = args[:-1], args[-1]
for arg in args: for arg in args:
if arg: if arg:
@@ -702,46 +707,31 @@ else:
'221 Goodbye.' '221 Goodbye.'
>>> >>>
''' '''
ssl_version = ssl.PROTOCOL_TLS_CLIENT
def __init__(self, host='', user='', passwd='', acct='', keyfile=None, def __init__(self, host='', user='', passwd='', acct='',
certfile=None, context=None, *, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT,
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None): source_address=None, encoding='utf-8'):
if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
if keyfile is not None or certfile is not None:
import warnings
warnings.warn("keyfile and certfile are deprecated, use a "
"custom context instead", DeprecationWarning, 2)
self.keyfile = keyfile
self.certfile = certfile
if context is None: if context is None:
context = ssl._create_stdlib_context(self.ssl_version, context = ssl._create_stdlib_context()
certfile=certfile,
keyfile=keyfile)
self.context = context self.context = context
self._prot_p = False self._prot_p = False
FTP.__init__(self, host, user, passwd, acct, timeout, source_address) super().__init__(host, user, passwd, acct,
timeout, source_address, encoding=encoding)
def login(self, user='', passwd='', acct='', secure=True): def login(self, user='', passwd='', acct='', secure=True):
if secure and not isinstance(self.sock, ssl.SSLSocket): if secure and not isinstance(self.sock, ssl.SSLSocket):
self.auth() self.auth()
return FTP.login(self, user, passwd, acct) return super().login(user, passwd, acct)
def auth(self): def auth(self):
'''Set up secure control connection by using TLS/SSL.''' '''Set up secure control connection by using TLS/SSL.'''
if isinstance(self.sock, ssl.SSLSocket): if isinstance(self.sock, ssl.SSLSocket):
raise ValueError("Already using TLS") raise ValueError("Already using TLS")
if self.ssl_version >= ssl.PROTOCOL_TLS: if self.context.protocol >= ssl.PROTOCOL_TLS:
resp = self.voidcmd('AUTH TLS') resp = self.voidcmd('AUTH TLS')
else: else:
resp = self.voidcmd('AUTH SSL') resp = self.voidcmd('AUTH SSL')
self.sock = self.context.wrap_socket(self.sock, self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
server_hostname=self.host)
self.file = self.sock.makefile(mode='r', encoding=self.encoding) self.file = self.sock.makefile(mode='r', encoding=self.encoding)
return resp return resp
@@ -778,7 +768,7 @@ else:
# --- Overridden FTP methods # --- Overridden FTP methods
def ntransfercmd(self, cmd, rest=None): def ntransfercmd(self, cmd, rest=None):
conn, size = FTP.ntransfercmd(self, cmd, rest) conn, size = super().ntransfercmd(cmd, rest)
if self._prot_p: if self._prot_p:
conn = self.context.wrap_socket(conn, conn = self.context.wrap_socket(conn,
server_hostname=self.host) server_hostname=self.host)
@@ -823,7 +813,6 @@ def parse227(resp):
'''Parse the '227' response for a PASV request. '''Parse the '227' response for a PASV request.
Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
Return ('host.addr.as.numbers', port#) tuple.''' Return ('host.addr.as.numbers', port#) tuple.'''
if resp[:3] != '227': if resp[:3] != '227':
raise error_reply(resp) raise error_reply(resp)
global _227_re global _227_re
@@ -843,7 +832,6 @@ def parse229(resp, peer):
'''Parse the '229' response for an EPSV request. '''Parse the '229' response for an EPSV request.
Raises error_proto if it does not contain '(|||port|)' Raises error_proto if it does not contain '(|||port|)'
Return ('host.addr.as.numbers', port#) tuple.''' Return ('host.addr.as.numbers', port#) tuple.'''
if resp[:3] != '229': if resp[:3] != '229':
raise error_reply(resp) raise error_reply(resp)
left = resp.find('(') left = resp.find('(')
@@ -865,7 +853,6 @@ def parse257(resp):
'''Parse the '257' response for a MKD or PWD request. '''Parse the '257' response for a MKD or PWD request.
This is a response to a MKD or PWD request: a directory name. This is a response to a MKD or PWD request: a directory name.
Returns the directoryname in the 257 reply.''' Returns the directoryname in the 257 reply.'''
if resp[:3] != '257': if resp[:3] != '257':
raise error_reply(resp) raise error_reply(resp)
if resp[3:5] != ' "': if resp[3:5] != ' "':

183
Lib/functools.py vendored
View File

@@ -10,9 +10,9 @@
# See C source code for _functools credits/copyright # See C source code for _functools credits/copyright
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', 'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
'partialmethod', 'singledispatch', 'singledispatchmethod', 'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
"cached_property"] 'cached_property']
from abc import get_cache_token from abc import get_cache_token
from collections import namedtuple from collections import namedtuple
@@ -30,7 +30,7 @@ from types import GenericAlias
# wrapper functions that can handle naive introspection # wrapper functions that can handle naive introspection
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__') '__annotations__', '__type_params__')
WRAPPER_UPDATES = ('__dict__',) WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper, def update_wrapper(wrapper,
wrapped, wrapped,
@@ -86,82 +86,86 @@ def wraps(wrapped,
# infinite recursion that could occur when the operator dispatch logic # infinite recursion that could occur when the operator dispatch logic
# detects a NotImplemented result and then calls a reflected method. # detects a NotImplemented result and then calls a reflected method.
def _gt_from_lt(self, other, NotImplemented=NotImplemented): def _gt_from_lt(self, other):
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).' 'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
op_result = self.__lt__(other) op_result = type(self).__lt__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result and self != other return not op_result and self != other
def _le_from_lt(self, other, NotImplemented=NotImplemented): def _le_from_lt(self, other):
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).' 'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
op_result = self.__lt__(other) op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other return op_result or self == other
def _ge_from_lt(self, other, NotImplemented=NotImplemented): def _ge_from_lt(self, other):
'Return a >= b. Computed by @total_ordering from (not a < b).' 'Return a >= b. Computed by @total_ordering from (not a < b).'
op_result = self.__lt__(other) op_result = type(self).__lt__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result return not op_result
def _ge_from_le(self, other, NotImplemented=NotImplemented): def _ge_from_le(self, other):
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).' 'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
op_result = self.__le__(other) op_result = type(self).__le__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result or self == other return not op_result or self == other
def _lt_from_le(self, other, NotImplemented=NotImplemented): def _lt_from_le(self, other):
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).' 'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
op_result = self.__le__(other) op_result = type(self).__le__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return op_result and self != other return op_result and self != other
def _gt_from_le(self, other, NotImplemented=NotImplemented): def _gt_from_le(self, other):
'Return a > b. Computed by @total_ordering from (not a <= b).' 'Return a > b. Computed by @total_ordering from (not a <= b).'
op_result = self.__le__(other) op_result = type(self).__le__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result return not op_result
def _lt_from_gt(self, other, NotImplemented=NotImplemented): def _lt_from_gt(self, other):
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).' 'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
op_result = self.__gt__(other) op_result = type(self).__gt__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result and self != other return not op_result and self != other
def _ge_from_gt(self, other, NotImplemented=NotImplemented): def _ge_from_gt(self, other):
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).' 'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
op_result = self.__gt__(other) op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other return op_result or self == other
def _le_from_gt(self, other, NotImplemented=NotImplemented): def _le_from_gt(self, other):
'Return a <= b. Computed by @total_ordering from (not a > b).' 'Return a <= b. Computed by @total_ordering from (not a > b).'
op_result = self.__gt__(other) op_result = type(self).__gt__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result return not op_result
def _le_from_ge(self, other, NotImplemented=NotImplemented): def _le_from_ge(self, other):
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).' 'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
op_result = self.__ge__(other) op_result = type(self).__ge__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result or self == other return not op_result or self == other
def _gt_from_ge(self, other, NotImplemented=NotImplemented): def _gt_from_ge(self, other):
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).' 'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
op_result = self.__ge__(other) op_result = type(self).__ge__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return op_result and self != other return op_result and self != other
def _lt_from_ge(self, other, NotImplemented=NotImplemented): def _lt_from_ge(self, other):
'Return a < b. Computed by @total_ordering from (not a >= b).' 'Return a < b. Computed by @total_ordering from (not a >= b).'
op_result = self.__ge__(other) op_result = type(self).__ge__(self, other)
if op_result is NotImplemented: if op_result is NotImplemented:
return op_result return op_result
return not op_result return not op_result
@@ -232,14 +236,14 @@ _initial_missing = object()
def reduce(function, sequence, initial=_initial_missing): def reduce(function, sequence, initial=_initial_missing):
""" """
reduce(function, sequence[, initial]) -> value reduce(function, iterable[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence, Apply a function of two arguments cumulatively to the items of a sequence
from left to right, so as to reduce the sequence to a single value. or iterable, from left to right, so as to reduce the iterable to a single
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items ((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the of the iterable in the calculation, and serves as a default when the
sequence is empty. iterable is empty.
""" """
it = iter(sequence) it = iter(sequence)
@@ -248,7 +252,8 @@ def reduce(function, sequence, initial=_initial_missing):
try: try:
value = next(it) value = next(it)
except StopIteration: except StopIteration:
raise TypeError("reduce() of empty sequence with no initial value") from None raise TypeError(
"reduce() of empty iterable with no initial value") from None
else: else:
value = initial value = initial
@@ -347,23 +352,7 @@ class partialmethod(object):
callables as instance methods. callables as instance methods.
""" """
def __init__(*args, **keywords): def __init__(self, func, /, *args, **keywords):
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor '__init__' of partialmethod "
"needs an argument")
elif 'func' in keywords:
func = keywords.pop('func')
self, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError("type 'partialmethod' takes at least one argument, "
"got %d" % (len(args)-1))
args = tuple(args)
if not callable(func) and not hasattr(func, "__get__"): if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor" raise TypeError("{!r} is not callable or a descriptor"
.format(func)) .format(func))
@@ -381,7 +370,6 @@ class partialmethod(object):
self.func = func self.func = func
self.args = args self.args = args
self.keywords = keywords self.keywords = keywords
__init__.__text_signature__ = '($self, func, /, *args, **keywords)'
def __repr__(self): def __repr__(self):
args = ", ".join(map(repr, self.args)) args = ", ".join(map(repr, self.args))
@@ -427,6 +415,7 @@ class partialmethod(object):
__class_getitem__ = classmethod(GenericAlias) __class_getitem__ = classmethod(GenericAlias)
# Helper functions # Helper functions
def _unwrap_partial(func): def _unwrap_partial(func):
@@ -503,7 +492,7 @@ def lru_cache(maxsize=128, typed=False):
with f.cache_info(). Clear the cache and statistics with f.cache_clear(). with f.cache_info(). Clear the cache and statistics with f.cache_clear().
Access the underlying function with f.__wrapped__. Access the underlying function with f.__wrapped__.
See: http://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
""" """
@@ -520,6 +509,7 @@ def lru_cache(maxsize=128, typed=False):
# The user_function was passed in directly via the maxsize argument # The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128 user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function) return update_wrapper(wrapper, user_function)
elif maxsize is not None: elif maxsize is not None:
raise TypeError( raise TypeError(
@@ -527,6 +517,7 @@ def lru_cache(maxsize=128, typed=False):
def decorating_function(user_function): def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function) return update_wrapper(wrapper, user_function)
return decorating_function return decorating_function
@@ -653,6 +644,15 @@ except ImportError:
pass pass
################################################################################
### cache -- simplified access to the infinity cache
################################################################################
def cache(user_function, /):
'Simple lightweight unbounded cache. Sometimes called "memoize".'
return lru_cache(maxsize=None)(user_function)
################################################################################ ################################################################################
### singledispatch() - single-dispatch generic function decorator ### singledispatch() - single-dispatch generic function decorator
################################################################################ ################################################################################
@@ -660,7 +660,7 @@ except ImportError:
def _c3_merge(sequences): def _c3_merge(sequences):
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm. """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
Adapted from http://www.python.org/download/releases/2.3/mro/. Adapted from https://www.python.org/download/releases/2.3/mro/.
""" """
result = [] result = []
@@ -740,6 +740,7 @@ def _compose_mro(cls, types):
# Remove entries which are already present in the __mro__ or unrelated. # Remove entries which are already present in the __mro__ or unrelated.
def is_related(typ): def is_related(typ):
return (typ not in bases and hasattr(typ, '__mro__') return (typ not in bases and hasattr(typ, '__mro__')
and not isinstance(typ, GenericAlias)
and issubclass(cls, typ)) and issubclass(cls, typ))
types = [n for n in types if is_related(n)] types = [n for n in types if is_related(n)]
# Remove entries which are strict bases of other entries (they will end up # Remove entries which are strict bases of other entries (they will end up
@@ -837,6 +838,17 @@ def singledispatch(func):
dispatch_cache[cls] = impl dispatch_cache[cls] = impl
return impl return impl
def _is_union_type(cls):
from typing import get_origin, Union
return get_origin(cls) in {Union, types.UnionType}
def _is_valid_dispatch_type(cls):
if isinstance(cls, type):
return True
from typing import get_args
return (_is_union_type(cls) and
all(isinstance(arg, type) for arg in get_args(cls)))
def register(cls, func=None): def register(cls, func=None):
"""generic_func.register(cls, func) -> func """generic_func.register(cls, func) -> func
@@ -844,9 +856,15 @@ def singledispatch(func):
""" """
nonlocal cache_token nonlocal cache_token
if func is None: if _is_valid_dispatch_type(cls):
if isinstance(cls, type): if func is None:
return lambda f: register(cls, f) return lambda f: register(cls, f)
else:
if func is not None:
raise TypeError(
f"Invalid first argument to `register()`. "
f"{cls!r} is not a class or union type."
)
ann = getattr(cls, '__annotations__', {}) ann = getattr(cls, '__annotations__', {})
if not ann: if not ann:
raise TypeError( raise TypeError(
@@ -859,12 +877,25 @@ def singledispatch(func):
# only import typing if annotation parsing is necessary # only import typing if annotation parsing is necessary
from typing import get_type_hints from typing import get_type_hints
argname, cls = next(iter(get_type_hints(func).items())) argname, cls = next(iter(get_type_hints(func).items()))
if not isinstance(cls, type): if not _is_valid_dispatch_type(cls):
raise TypeError( if _is_union_type(cls):
f"Invalid annotation for {argname!r}. " raise TypeError(
f"{cls!r} is not a class." f"Invalid annotation for {argname!r}. "
) f"{cls!r} not all arguments are classes."
registry[cls] = func )
else:
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} is not a class."
)
if _is_union_type(cls):
from typing import get_args
for arg in get_args(cls):
registry[arg] = func
else:
registry[cls] = func
if cache_token is None and hasattr(cls, '__abstractmethods__'): if cache_token is None and hasattr(cls, '__abstractmethods__'):
cache_token = get_cache_token() cache_token = get_cache_token()
dispatch_cache.clear() dispatch_cache.clear()
@@ -925,18 +956,16 @@ class singledispatchmethod:
################################################################################ ################################################################################
### cached_property() - computed once per instance, cached as attribute ### cached_property() - property result cached as instance attribute
################################################################################ ################################################################################
_NOT_FOUND = object() _NOT_FOUND = object()
class cached_property: class cached_property:
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
self.attrname = None self.attrname = None
self.__doc__ = func.__doc__ self.__doc__ = func.__doc__
self.lock = RLock()
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
if self.attrname is None: if self.attrname is None:
@@ -963,19 +992,15 @@ class cached_property:
raise TypeError(msg) from None raise TypeError(msg) from None
val = cache.get(self.attrname, _NOT_FOUND) val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND: if val is _NOT_FOUND:
with self.lock: val = self.func(instance)
# check if another thread filled cache while we awaited lock try:
val = cache.get(self.attrname, _NOT_FOUND) cache[self.attrname] = val
if val is _NOT_FOUND: except TypeError:
val = self.func(instance) msg = (
try: f"The '__dict__' attribute on {type(instance).__name__!r} instance "
cache[self.attrname] = val f"does not support item assignment for caching {self.attrname!r} property."
except TypeError: )
msg = ( raise TypeError(msg) from None
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
return val return val
__class_getitem__ = classmethod(GenericAlias) __class_getitem__ = classmethod(GenericAlias)

Some files were not shown because too many files have changed in this diff Show More